/*-*-C-*-
 * Generic gadgetry for Window CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dbox.h"
#include "interactor.h"
#include "registry.h"
#include "toolbox.h"

#include "format.h"
#include "relocate.h"
#include "windowedit.h"
#include "colours.h"
#include "gadgdefs.h"
#include "gadget.h"
#include "gui.h"
#include "icondefs.h"
#include "props.h"
#include "protocol.h"


/*
 * These #define's specify the width of the extra border around default
 *  action buttons in OS units.
 */

#define  DEFAULT_AB_BORDER_X  8
#define  DEFAULT_AB_BORDER_Y  8



/*
 * Returns the address of the definition of gadgets of type 'type'.
 * If no such definition exists, a NULL pointer is returned.
 */

static GadgetDefPtr find_gadget_def (int type)
{
    GadgetDefPtr def = gadgetdefs;

    while (def->type != -1)
    {
        if (def->type == type)
            return def;
        def++;
    }

    return NULL;
}


/*
 * Creates a description of the body of an unknown gadget type by examining
 *  the relocations table.
 */

static error * create_body_description (
    GadgetTemplatePtr template,     /* unknown gadget template address */
    BodyDefPtr bodydef,             /* definition to be completed */
    int offset,                     /* of gadget from template body start */
    RelocationTablePtr reloctable   /* the template's relocation table */
)
{
    int base = offset + sizeof (GadgetTemplateHeaderRec); /* these delimit */
    int top = offset + template->hdr.size;                /* template body */
    int numrelocs = reloctable->numrelocations;
    RelocationPtr reloc = reloctable->relocations;
    int numrefs = 1;
    RefDefPtr refs;
    int i;

    /* determine how many RefDefRec's are needed */
    for (i = 0; i < numrelocs; i++)
    {
        int loc = reloc[i].wordtorelocate;

        if (loc >= base && loc < top)
            numrefs++;
    }

    /* allocate RefDefRec array */
    refs = (RefDefPtr) calloc (numrefs, sizeof (RefDefRec));
    if (refs == NULL)
        return error_lookup ("NoMem");

    /* initialise fields of body definition */
    bodydef->size = top - base;
    bodydef->usecount = 0;
    bodydef->refs = refs;

    /* fill in the refs array */
    for (i = 0; i < numrelocs; i++)
    {
        int loc = reloc[i].wordtorelocate;

        if (loc >= base && loc < top)
        {
            refs->offset = loc - base;
            refs->emptyisnull = FALSE;

            switch (reloc[i].directive)
            {
            case RELOCATE_STRINGREFERENCE:
                refs->type = REF_STR;
                break;

            case RELOCATE_MSGREFERENCE:
                refs->type = REF_MSG;
                break;

            case RELOCATE_SPRITEAREAREFERENCE:
                refs->type = REF_SPRITE;
                break;

            default:
                free (bodydef->refs);
                return error_lookup ("BadUnkGadReloc", reloc[i].directive);
            }

            refs++;
        }
    }

    /* and terminate it */
    refs->type = REF_END;

    return NULL;
}


/*
 * Creates a gadget record from the relocated gadget template 'template'.
 *
 * Returns the gadget record's address, or NULL if no memory available.
 *
 * Copies are made of any strings, and the coordinates of the gadget's
 *  bounding box are forced to be multiples of 4.
 */

GadgetPtr gadget_load (
    GadgetTemplatePtr template,
    int offset,                      /* needed to decode unknown */
    RelocationTablePtr reloctable    /* gadget types - see above */ 
)
{
    GadgetDefPtr def;
    GadgetPtr gadget;

    /* locate the gadget type's definition */
    def = find_gadget_def (template->hdr.type);

    /* create definition if it's an unknown type of gadget */
    if (def == NULL)
    {
        error * err;

        /* allocate a new GadgetDefRec */
        def = malloc (sizeof (GadgetDefRec));
        if (def == NULL)
            return NULL;

        /* and initialise its fields from the prototype */
        *def = unkgadgetdef;

        /* and add a description of any relocations */
        err = create_body_description (template, &def->body,
                                       offset, reloctable);
        if (err != NULL)
        {
            error_box (err);
            return NULL;
        }
    }

    /* allocate space for the new gadget record */
    gadget = calloc (offsetof (GadgetRec, hdr) +     /* internal fields */
                     sizeof (GadgetHeaderRec) +      /* gadget header */
                     def->body.size, 1);             /* gadget body */
    if (gadget == NULL)
        return NULL;

    /* initialise the gadget header */
    {
        GadgetHeaderPtr hdr = &gadget->hdr;

        /* straight copy of all fields */
        *hdr = *( (GadgetHeaderPtr) &template->hdr );

        /* check gadget size field - must be zero or correct! */
        {
            int size = gadget->hdr.size;

            if ( size != 0 &&
                 size != (sizeof (GadgetHeaderRec) + def->body.size) )
            {
                error_box (error_lookup ("BadSize", size, gadget->hdr.type));
                return NULL;
            }
        }

        /* ensure all extent coordinates are multiples of 4 */
        wimp_align_rect (&hdr->bbox);

        /* copy any help message present */
        if (clonestring (&hdr->helpmessage) != NULL)
            return NULL;

        /* and initialise "local" copy of maxhelp (which may be "-1") */
        gadget->localmaxhelp = gui_load_len_field (hdr->helpmessage,
                                                   &hdr->maxhelp);
    }

    /* and the gadget body */
    {
        char *body = (char *) &gadget->body;
        char *hdr = (char *) &gadget->hdr;

        /* straight copy of all fields */
        memcpy (body, (char *) &template->body, def->body.size);

        /* take copies of any strings */
        {
            RefDefPtr refs = def->body.refs;

            while (refs->type != REF_END)
            {
                switch (refs->type)
                {
                case REF_STR:
                case REF_MSG:
                case REF_STRORMSG:
                    {
                        char **strloc = (char **) (body + refs->offset);

                        if (refs->emptyisnull && **strloc == 0)
                            *strloc = NULL;
                        else
                        {
                            if (clonestring (strloc) != NULL)
                                return NULL;
                        }
                    }
                }

                refs++;
            }
        }

        /*
         * Determine if this gadget needs an extension record. If so, create
         *  it and initialise its contents.
         *
         * For a normal gadget, the extension record contains the values of
         *  "length" fields which may be treated as "-1" values internally;
         *  these must be copies of the real values, because otherwise
         *  Window_PlotGadget falls over.
         */

        {
            FieldDefPtr field = def->fields;
            int numlenfields = 0;

            /* determine the size of any extension record */
            while (field->type != FLD_END)
            {
                switch (field->type)
                {
                case FLD_MAND_ASS_STR:
                case FLD_OPT_ASS_STR:
                case FLD_ALLOWABLE:
                case FLD_LENGTH:
                    numlenfields++;
                }
                field++;
            }
            gadget->extensionsize = numlenfields * sizeof(int);

            if (numlenfields != 0)
            {
                int *extra = (int *) malloc (gadget->extensionsize);
                int next = 0;

                /* allocate extension record */
                gadget->extension = extra;

                if (extra == NULL)
                    return NULL;

                /* fill in the extension record entries */
                field = def->fields;
                while (field->type != FLD_END)
                {
                    switch (field->type)
                    {
                    case FLD_MAND_ASS_STR:
                        {
                            MandAssStrFldPtr f = &field->def->mandassstr;

                            extra[next++] = gui_load_len_field (
                                            *((char **)(hdr + f->valoffset)),
                                                (int *)(hdr + f->lenoffset));
                        }
                        break;

                    case FLD_OPT_ASS_STR:
                        {
                            OptAssStrFldPtr f = &field->def->optassstr;

                            extra[next++] = gui_load_len_field (
                                            *((char **)(hdr + f->valoffset)),
                                                (int *)(hdr + f->lenoffset));
                        }
                        break;

                    case FLD_ALLOWABLE:
                        {
                            AllowableFldPtr f = &field->def->allowable;

                            extra[next++] = gui_load_len_field (
                                            *((char **)(hdr + f->valoffset)),
                                                (int *)(hdr + f->lenoffset));
                        }
                        break;

                    case FLD_LENGTH:
                        {
                            LengthFldPtr f = &field->def->length;
                            int min = (f->f) (gadget);
                            int *valp = (int *)(hdr + f->offset);

                            if (min > *valp)
                                *valp = min;

                            extra[next++] = (min == *valp) ? -1 : *valp;
                        }
                        break;
                    }

                    field++;
                }
            }
        }

    }

    /* and link gadget record to its definition */
    gadget->def = def;

    return gadget;
}


/*
 * The gadget record is re-assembled into the supplied template. Entries are
 * added to the strings, messages and relocation tables as necessary, and the
 * fields inside 'tip' are updated to reflect the new states of these tables.
 */

void gadget_save (
    TemplateInfoPtr tip,
    GadgetPtr gadget,
    GadgetTemplatePtr template
)
{
    /* straight copy of the header fields */
    template->hdr = *( (GadgetTemplateHeaderPtr) &gadget->hdr );

    /* set the gadget size field explicitly in case it was zero before */
    template->hdr.size = sizeof (GadgetHeaderRec) + gadget->def->body.size;

    /* relocate the help message field */
    relocate_make_ref (tip,
        RELOCATE_MSGREFERENCE,
        (int *) &template->hdr.helpmessage, gadget->hdr.helpmessage);

    /* reassemble gadget body */
    {
        GadgetDefPtr def = gadget->def;
        char *body = (char *) &gadget->body;
        char *templbody = (char *) &template->body;

        /* straight copy of all fields */
        memcpy (templbody, body, def->body.size);

        /* relocate any strings in the body */
        {
            RefDefPtr ref = def->body.refs;

            while (ref->type != REF_END)
            {
                int reloctype = RELOCATE_STRINGREFERENCE;

                if ( ref->type == REF_MSG ||
                     ( ref->type == REF_STRORMSG &&
                       (gadget->hdr.flags & ref->mask) == ref->ismsg ) )
                    reloctype = RELOCATE_MSGREFERENCE;

                if ( ref->type == REF_SPRITE)
                    reloctype = RELOCATE_SPRITEAREAREFERENCE;

                relocate_make_ref (
                    tip,
                    reloctype,
                    (int *) (templbody + ref->offset),
                    *( (char **) (body + ref->offset) ));

                ref++;
            }
        }
    }

    return;
}


/*
 * Updates the supplied arguments according to the space required to store
 * the gadget record 'gadget' as a template.
 */

void gadget_size (
    GadgetPtr gadget,
    int *bodysize,
    int *stringsize,
    int *msgsize,
    int *numrelocs
)
{
    /* first the gadget header */
    *bodysize += sizeof (GadgetTemplateHeaderRec);
    *numrelocs += 1;
    if (gadget->hdr.helpmessage)
        *msgsize += strlen (gadget->hdr.helpmessage) + 1;

    /* then the gadget body */
    {
        GadgetDefPtr def = gadget->def;
        char *body = (char *) &gadget->body;

        *bodysize += def->body.size;

        {
            RefDefPtr ref = def->body.refs;

            while (ref->type != REF_END)
            {
                if (ref->type != REF_SPRITE)
                {
                    int reloctype = RELOCATE_STRINGREFERENCE;
                    int size = 0;
                    char *s = *( (char **)(body + ref->offset) );

                    if ( ref->type == REF_MSG ||
                         ( ref->type == REF_STRORMSG &&
                           (gadget->hdr.flags & ref->mask) == ref->ismsg ) )
                        reloctype = RELOCATE_MSGREFERENCE;


                    if (s != NULL)
                        size = strlen (s) + 1;

                    if (reloctype == RELOCATE_STRINGREFERENCE)
                        *stringsize += size;
                    else
                        *msgsize += size;
                }

                (*numrelocs)++;
                ref++;
            }
        }
    }

    return;
}


/*
 * Create a new gadget record that is a copy of the given one; return NULL
 *  if insufficient memory is available.
 */

GadgetPtr gadget_copy (GadgetPtr gadget)
{
    GadgetPtr new;
    GadgetDefPtr def = gadget->def;

    /* allocate space for the new gadget record */
    new = calloc (offsetof (GadgetRec, hdr) +     /* internal fields */
                  sizeof (GadgetHeaderRec) +      /* gadget header */
                  def->body.size, 1);             /* gadget body */
    if (new == NULL)
        return NULL;

    /* copy fields outside the hdr and body sub-records */
    new->def = gadget->def;
    new->localmaxhelp = gadget->localmaxhelp;

    /*
     * The treatment of the extension record depends on the kind of gadget:
     *  For normal gadgets, the extension record is cloned.
     *  For unknown gadgets, the extension record is not copied (since it is
     *   really part of the associated dbox).
     */
    if (def->type != GADGET_UNKNOWN_TYPE)
    {
        new->extensionsize = gadget->extensionsize;
        if (gadget->extensionsize != 0)
        {
            char *extra = malloc (gadget->extensionsize);

            if (extra == NULL)
                return NULL;

            memcpy (extra, (char *)gadget->extension, gadget->extensionsize);
            new->extension = (int *) extra;
        }
    }


    /* make a straight copy of the header */
    new->hdr = gadget->hdr;

    /* and clone the help message string */
    if (clonestring (&new->hdr.helpmessage) != NULL)
        return NULL;

    /* now copy the gadget body */
    {
        char *body = (char *) &new->body;

        /* straight copy of all fields */
        memcpy (body, (char *) &gadget->body, def->body.size);

        /* take copies of any strings */
        {
            RefDefPtr refs = def->body.refs;

            while (refs->type != REF_END)
            {
                if (refs->type != REF_SPRITE)
                {
                    if (clonestring ((char **) (body + refs->offset))
                                                                  != NULL)
                        return NULL;
                }

                refs++;
            }
        }
    }

    /* increase usecount field for unknown gadgets */
    if (def->type == GADGET_UNKNOWN_TYPE)
        def->body.usecount++;
 
    return new;
}


/*
 * Any space malloc'd for fields within the gadget record is free'd.
 * Any dialogue box opened for the gadget is free'd.
 * And, finally, the gadget record itself is free'd.
 */

void gadget_free (GadgetPtr gadget)

{
    /* deregister, delete and free any dbox associated with the gadget */
    if (gadget->dbox)
        gadget_close_dbox (gadget);

    /* free any help message string in the gadget header */
    free (gadget->hdr.helpmessage);

    /* free any extension record referenced by the gadget */
    free (gadget->extension);

    /* free any strings in the gadget body */
    {
        char *body = (char *) &gadget->body;
        RefDefPtr refs = gadget->def->body.refs;

        while (refs->type != REF_END)
        {
            if (refs->type != REF_SPRITE)
                free ( *( (char **) (body + refs->offset) ) );
            refs++;
        }
    }

    /* free the RefDefRecs and the GadgetDefRec itself in an unknown gadget
       if the usecount is 0 */
    if (gadget->def->type == GADGET_UNKNOWN_TYPE)
    {
        if (gadget->def->body.usecount == 0)
        {
            free (gadget->def->body.refs);
            free (gadget->def);
        }
        else
            gadget->def->body.usecount--;
    }

    /* and finally free the entire gadget record itself */
    free (gadget);

    return;
}


/*
 * Initialises the icons in the properties dbox for the given gadget.
 *
 * Called from:
 *   gadget_open_dbox(..) - after the properties dbox has been created and
 *    opened.
 *   gadget_dbox_mouse_click(..) - after the user has clicked ADJ on the
 *    Cancel or OK action buttons.
 *   gadget_dbox_key_pressed(..) - after the user has pressed SHIFT-RETURN.
 */

static error * gadget_init_dbox (GadgetPtr gadget)
{
    char *body = (char *) &gadget->hdr;
    WindowPtr dbox = gadget->dbox;
    GadgetDefPtr def = gadget->def;
    FieldDefPtr field = def->fields;
    int *extra = gadget->extension;
    int next = 0;

    /* initialise the fields common to all gadgets */

    /* component id */
    dbox_sethex (dbox, def->icons.id, gadget->hdr.componentID);

    /* help message */
    gui_put_len_opt_str (dbox,
                         def->icons.hashelp,
                         def->icons.help,
                         def->icons.helpmax,
                         gadget->hdr.helpmessage, gadget->localmaxhelp);

    /* window name */
    dbox_setstring (dbox, def->icons.window, gadget->owner->name);

    /* faded bit */
    GUI_PUT_FLAG(dbox, def->icons.faded, gadget->hdr.flags, GADGET_FADED);


    while (field->type != FLD_END)
    {
        switch (field->type)
        {
        case FLD_INTEGER:
            {
                IntegerFldPtr f = &field->def->integer;

                (f->displayashex ? dbox_sethex : dbox_setint)
                     ( dbox, f->valicon, *((int *)(body + f->offset)) );
            }
            break;

        case FLD_MAND_ASS_STR:
            {
                MandAssStrFldPtr f = &field->def->mandassstr;

                gui_put_len_str (dbox,
                                 f->valicon, f->lenicon,
                                 *((char **)(body + f->valoffset)),
                                 extra[next++]);
            }
            break;

        case FLD_OPT_ASS_STR:
            {
                OptAssStrFldPtr f = &field->def->optassstr;

                gui_put_len_opt_str (dbox,
                                     f->opticon, f->valicon, f->lenicon,
                                     *((char **)(body + f->valoffset)),
                                     extra[next++]);
            }
            break;

        case FLD_MAND_CONST_STR:
            {
                MandConstStrFldPtr f = &field->def->mandconststr;

                gui_put_str (dbox,
                             f->icon,
                             *((char **)(body + f->offset)) );
            }
            break;

        case FLD_OPT_CONST_STR:
            {
                OptConstStrFldPtr f = &field->def->optconststr;

                gui_put_opt_str (dbox,
                                 f->opticon, f->valicon,
                                 *((char **)(body + f->offset)) );
            }
            break;

        case FLD_FLAG:
            {
                FlagFldPtr f = &field->def->flag;
                Bool flagmeanson = f->flagmeanson;
                unsigned int flag = gadget->hdr.flags & f->mask;

                dbox_setbutton (dbox, f->opticon,
                                (flag != 0) ? flagmeanson : !flagmeanson);
            }
            break;

        case FLD_XFLAG:
            {
                XFlagFldPtr f = &field->def->xflag;
                Bool flagmeanson = f->flagmeanson;
                unsigned flag = f->mask & *((unsigned *)(body + f->offset));

                dbox_setbutton (dbox, f->opticon,
                                (flag != 0) ? flagmeanson : !flagmeanson);
            }
            break;

        case FLD_XPACKED:
            {
                XPackedFldPtr f = &field->def->xpacked;
                unsigned word = *((unsigned *)(body + f->offset));

                dbox_setint (dbox, f->valicon, (word & f->mask) >> f->shift);
            }
            break;

        case FLD_BITS:
            {
                BitsFldPtr f = &field->def->bits;

                dbox_setbutton (dbox, f->opticon,
                                (gadget->hdr.flags & f->mask) == f->value);
            }
            break;

        case FLD_MAND_EVENT:
            {
                MandEventFldPtr f = &field->def->mandevent;

                gui_put_mand_event ( dbox,
                                     f->dflticon, f->othericon,
                                     f->valicon,
                                     *((int *)(body + f->offset)) );
            }
            break;

        case FLD_OPT_EVENT:
            {
                OptEventFldPtr f = &field->def->optevent;

                gui_put_opt_event ( dbox,
                                    f->dflticon, f->othericon, f->noneicon,
                                    f->valicon,
                                    *((int *)(body + f->offset)),
                                    (gadget->hdr.flags & f->mask) == 0 );
            }
            break;

        case FLD_ALLOWABLE:
            {
                AllowableFldPtr f = &field->def->allowable;

                gui_put_allowable ( dbox,
                                    f->opticon,
                                    f->lcicon, f->ucicon, f->numicon,
                                    f->otheropticon, f->othervalicon,
                                    f->lenicon, f->upicon, f->downicon,
                                    *((char **)(body + f->valoffset)),
                                    extra[next++]);
            }
            break;

        case FLD_LINK:
            {
                LinkFldPtr f = &field->def->link;

                gui_put_link ( dbox,
                               f->opticon, f->valicon,
                               *((int *)(body + f->valoffset)) );
            }
            break;

        case FLD_COLOUR:
            {
                ColourFldPtr f = &field->def->colour;

                gui_put_colour (dbox,
                                f->valicon,
                                (gadget->hdr.flags & f->mask) >> f->shift,
                                FALSE);
            }
            break;

        case FLD_XCOLOUR:
            {
                XColourFldPtr f = &field->def->xcolour;
                unsigned word = *((unsigned *)(body + f->offset));

                gui_put_colour (dbox,
                                f->valicon,
                                (word & f->mask) >> f->shift,
                                FALSE);
            }
            break;

        case FLD_LENGTH:
            {
                LengthFldPtr f = &field->def->length;
                int val = extra[next++];

                if (val == -1)
                    dbox_setstring (dbox, f->icon, "*");
                else
                    dbox_setint (dbox, f->icon, val);
            }
            break;

        default:
            return error_lookup ("UnkFldType", field->type);
        }

        field++;
    }


    /* any special processing required? */
    if (def->specialinit != NULL)
        ER ( (def->specialinit) (gadget) );

    return NULL;
}


/*
 * Result is TRUE iff 'old' is identical to 'new'; both are gadgets.
 * 
 * All space occupied by 'old' is released.
 */

static Bool compare_gadgets (
    GadgetPtr old,
    GadgetPtr new
)
{
    GadgetDefPtr def = new->def;
    Bool ok = TRUE;

    /* compare relevant fields outside hdr and body sub-records */
    if (new->localmaxhelp != old->localmaxhelp)
        ok = FALSE;

    /*
     * Treatment of the extension record depends on the kind of gadget:
     *  For normal gadgets, the extension records are compared, and the
     *   original one is freed.
     *  For unknown gadgets, no comparisons are made; nothing needs to be
     *   freed either, because no extension record will have been included
     *   in the original copy (see gadget_copy(..)).
     */
    if (def->type != GADGET_UNKNOWN_TYPE)
    { 
        char *oldx = (char *) old->extension;
        char *newx = (char *) new->extension;

        if (memcmp (oldx, newx, new->extensionsize) != 0)
            ok = FALSE;

        free (oldx);
    }
        
    /* compare and free header fields */
    if (!equalstrings (old->hdr.helpmessage, new->hdr.helpmessage))
        ok = FALSE;
    free (old->hdr.helpmessage);
    old->hdr.helpmessage = new->hdr.helpmessage; /* ready for compare */

    if (memcmp ((char *) &old->hdr, (char *) &new->hdr,
                                     sizeof (GadgetHeaderRec)) != 0)
        ok = FALSE;

    /* compare and free all string fields in gadget body */
    {
        RefDefPtr refs = def->body.refs;
        char *oldbody = (char *) &old->body;
        char *newbody = (char *) &new->body;

        while (refs->type != REF_END)
        {
            if (refs->type != REF_SPRITE)
            {
                char **oldstr = (char **) (oldbody + refs->offset);
                char **newstr = (char **) (newbody + refs->offset);

                if (!equalstrings (*oldstr, *newstr))
                    ok = FALSE;
                free (*oldstr);
                *oldstr = *newstr;  /* makes comparison below safe */
            }

            refs++;
        }

        /* compare the rest of the body */
        if (memcmp (oldbody, newbody, def->body.size) != 0)
            ok = FALSE;
    }

    /* and free it */
    free (old);

    /* reduce usecount for unknown gadget types */
    if (def->type == GADGET_UNKNOWN_TYPE)
        def->body.usecount--;

    return ok;
}


/*
 * Updates the given gadget according to the values in the icons in its
 *  properties dbox; the gadget is redrawn.
 *
 * Called from:
 *   gadget_dbox_mouse_click(..) - after the user has clicked on the OK
 *    action button.
 *   gadget_dbox_key_pressed(..) - after the user has pressed SHIFT-RETURN.
 */

static error * gadget_apply_dbox (GadgetPtr gadget)
{
    char *body = (char *) &gadget->hdr;
    WindowPtr dbox = gadget->dbox;
    GadgetDefPtr def = gadget->def;
    FieldDefPtr field = def->fields;
    int *extra = gadget->extension;
    int next = 0;
    GadgetPtr copy;

    /* first take a copy for later comparison to see if anything changes */
    copy = gadget_copy (gadget);
    if (copy == NULL)
        return error_lookup ("NoMem");


    /* any special processing required? */
    if (def->specialapply != NULL)
    {
        error *err = (def->specialapply) (gadget);

        if (err != NULL)
        {
            gadget_free (copy);
            return err;
        }
    }


    /* update the fields common to all gadgets */

    /* component id */
    {
        int newid = dbox_getint (dbox, def->icons.id);

        /* check that any new componentID is not ambiguous */
        if (gadget->hdr.componentID != newid)
        {
            GadgetPtr g = gadget->owner->gadgets;

            while (g != NULL)
            {
                if (g->hdr.componentID == newid)     /* is it a duplicate? */
                {
                    gadget_free (copy);
                    return error_lookup ("CompID", newid);
                }
                g = g->next;
            }
            gadget->hdr.componentID = newid;
        }
    }

    /* help message */
    gui_get_len_opt_str (dbox,
                         def->icons.hashelp,
                         def->icons.help,
                         def->icons.helpmax,
                         &gadget->hdr.helpmessage, &gadget->localmaxhelp);
    gadget->hdr.maxhelp = gui_save_len_field (gadget->hdr.helpmessage,
                                              gadget->localmaxhelp);

    /* faded bit */
    GUI_GET_FLAG(dbox, def->icons.faded, gadget->hdr.flags, GADGET_FADED);


    while (field->type != FLD_END)
    {
        switch (field->type)
        {
        case FLD_INTEGER:
            {
                IntegerFldPtr f = &field->def->integer;

                *((int *)(body + f->offset)) = 
                    dbox_getint (dbox, f->valicon);
            }
            break;

        case FLD_MAND_ASS_STR:
            {
                MandAssStrFldPtr f = &field->def->mandassstr;

                gui_get_len_str (dbox,
                                 f->valicon, f->lenicon,
                                 (char **)(body + f->valoffset),
                                 extra + next);
                *(int *)(body + f->lenoffset) =
                    gui_save_len_field (*(char **)(body + f->valoffset),
                                        extra[next++]);
            }
            break;

        case FLD_OPT_ASS_STR:
            {
                OptAssStrFldPtr f = &field->def->optassstr;

                gui_get_len_opt_str (dbox,
                                     f->opticon, f->valicon, f->lenicon,
                                     (char **)(body + f->valoffset),
                                     extra + next);
                *(int *)(body + f->lenoffset) =
                    gui_save_len_field (*(char **)(body + f->valoffset),
                                        extra[next++]);
            }
            break;

        case FLD_MAND_CONST_STR:
            {
                MandConstStrFldPtr f = &field->def->mandconststr;

                gui_get_str (dbox,
                             f->icon,
                             (char **)(body + f->offset) );
            }
            break;

        case FLD_OPT_CONST_STR:
            {
                OptConstStrFldPtr f = &field->def->optconststr;

                gui_get_opt_str (dbox,
                                 f->opticon, f->valicon,
                                 (char **)(body + f->offset) );
            }
            break;

        case FLD_FLAG:
            {
                FlagFldPtr f = &field->def->flag;
                Bool flagmeanson = f->flagmeanson;
                Bool flag = dbox_getbutton (dbox, f->opticon);

                if (flagmeanson && flag || !flagmeanson && !flag)
                    gadget->hdr.flags |= f->mask;
                else
                    gadget->hdr.flags &= ~f->mask;
            }
            break;

        case FLD_XFLAG:
            {
                XFlagFldPtr f = &field->def->xflag;
                Bool flagmeanson = f->flagmeanson;
                Bool flag = dbox_getbutton (dbox, f->opticon);
                unsigned *flags = (unsigned *)(body + f->offset);

                if (flagmeanson && flag || !flagmeanson && !flag)
                    *flags |= f->mask;
                else
                    *flags &= ~f->mask;
            }
            break;

        case FLD_XPACKED:
            {
                XPackedFldPtr f = &field->def->xpacked;
                unsigned val = dbox_getint (dbox, f->valicon);
                unsigned *word = (unsigned *)(body + f->offset);

                *word = (*word & ~f->mask) | (val << f->shift);
            }
            break;

        case FLD_BITS:
            {
                BitsFldPtr f = &field->def->bits;

                if (dbox_getbutton (dbox, f->opticon))
                    gadget->hdr.flags =
                        gadget->hdr.flags & ~f->mask | f->value;
            }
            break;

        case FLD_MAND_EVENT:
            {
                MandEventFldPtr f = &field->def->mandevent;

                gui_get_mand_event ( dbox,
                                     f->dflticon,
                                     f->valicon,
                                     (int *)(body + f->offset) );
            }
            break;

        case FLD_OPT_EVENT:
            {
                OptEventFldPtr f = &field->def->optevent;

                gui_get_opt_event ( dbox,
                                    f->dflticon, f->noneicon,
                                    f->valicon,
                                    (int *)(body + f->offset),
                                    &gadget->hdr.flags, f->mask );
            }
            break;

        case FLD_ALLOWABLE:
            {
                AllowableFldPtr f = &field->def->allowable;

                gui_get_allowable ( dbox,
                                    f->opticon,
                                    f->lcicon, f->ucicon, f->numicon,
                                    f->otheropticon, f->othervalicon,
                                    f->lenicon, f->upicon, f->downicon,
                                    (char **)(body + f->valoffset),
                                    extra + next);
                *(int *)(body + f->lenoffset) =
                    gui_save_len_field (*(char **)(body + f->valoffset),
                                        extra[next++]);
            }
            break;

        case FLD_LINK:
            {
                LinkFldPtr f = &field->def->link;

                gui_get_link ( dbox,
                               f->opticon, f->valicon,
                               (int *)(body + f->valoffset) );
            }
            break;

        case FLD_COLOUR:
            {
                ColourFldPtr f = &field->def->colour;
                unsigned colour = gui_get_colour (dbox, f->valicon);

                gadget->hdr.flags = (gadget->hdr.flags & ~f->mask) |
                                    (colour << f->shift);
            }
            break;

        case FLD_XCOLOUR:
            {
                XColourFldPtr f = &field->def->xcolour;
                unsigned colour = gui_get_colour (dbox, f->valicon);
                unsigned *word = (unsigned *)(body + f->offset);

                *word = (*word & ~f->mask) | (colour << f->shift);
            }
            break;

        case FLD_LENGTH:
            {
                LengthFldPtr f = &field->def->length;
                int min = (f->f) (gadget);
                char *s = dbox_getstring (dbox, f->icon);
                int *val = (int *)(body + f->offset);

                if (*s == '*')
                {
                    extra[next++] = -1;
                    *val = min;
                }
                else
                {
                    int len = dbox_getint (dbox, f->icon);

                    if (min > len)
                        len = min;

                    extra[next++] = len;
                    *val = len;
                }
            }
            break;

        default:
            gadget_free (copy);
            return error_lookup ("UnkFldType", field->type);
        }

        field++;
    }


    /* see if gadget has been altered in any way */
    if (!compare_gadgets (copy, gadget))
    {
        /* force redraw of gadget */
        {
            RectRec bbox = gadget->hdr.bbox;

            if (gadget->selected)
                windowedit_add_ears_to_bbox (&bbox, &bbox);
            wimp_invalidate (gadget->owner->window, &bbox);
        }

        /* inform shell that window object may have been modified */
        protocol_send_resed_object_modified (gadget->owner);
    }

    return NULL;
}


/*
 * Create and open the gadget's properties dbox.
 *
 * If the corresponding properties dbox is already open, it is simply raised
 *  to the top of the window stack.
 *
 * Otherwise, a new properties window is created and opened.
 *
 * Any new window will be displayed at the same position as the most recent
 *  position of any other window created from the same template; this is
 *  achieved by copying co-ordinates to the template whenever an existing
 *  window is reopened - see gadget_reopen_dbox(..).
 *
 * Called from:
 *   windowmenu_cb(..) in c.windowedit - after the user has chosen the
 *    "Properties..." entry from the "Edit" submenu for the gadget.
 *   windowedit_mouse_click(..) - after the user has double-select-clicked
 *    on the gadget.
 */

error * gadget_open_dbox (GadgetPtr gadget)
{
    if (gadget->dbox == NULL)
    {
        ER ( wimp_copy_template (gadget->def->proto, &gadget->dbox,
                                 gadget->def->protosize) );
        ER ( swi (Wimp_CreateWindow,  R1, &gadget->dbox->visarea,
                        OUT,  R0, &gadget->dbox->handle,  END) );
        ER ( registry_register_window (gadget->dbox->handle,
                                       GadgetDbox, (void *) gadget) );

        /* create cleared extension record for unknown gadget */
        if (gadget->def->type == GADGET_UNKNOWN_TYPE)
        {
            int size = gadget->def->body.size;
            int *bodycopy = NULL;

            if (size > 0)
            {
                bodycopy = calloc (size, 1);

                if (bodycopy == NULL)
                    return error_lookup ("NoMem");
            }

            gadget->extension = bodycopy;
            gadget->extensionsize = size;

            /* and set window title */
            {
                char buf[256];
                sprintf (buf, dbox_gettitle (gadget->def->proto),
                                                     gadget->hdr.type);
                ER ( dbox_settitle (gadget->dbox, buf, FALSE) );
            }
        }

        gadget->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, gadget->dbox,  END) );

        ER ( gadget_init_dbox (gadget) );
    }
    else
    {
        gadget->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, gadget->dbox,  END) );
    }   

    return dbox_set_caret_to (gadget->dbox,
                              gadget->def->icons.firstwritable);
}


/*
 * Close and delete the gadget's properties dbox.
 *
 * Called from:
 *   close_window_request(..) in c.main - when the user clicks on the dbox's
 *    close icon [not at present].
 *   gadget_free(..) - which is called when the gadget's parent's editing
 *    window is closed.
 *   gadget_dbox_mouse_click(..) - when the user select-clicks on the OK or
 *    Cancel action buttons.
 */

error * gadget_close_dbox (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;

    /* de-register the window */
    ER ( registry_deregister_window (dbox->handle) );

    /* hide and delete the window */

    /*
     * This service call is fielded by BorderUtils 0.05 which fixes
     *  a bug associated with the deletion of windows which have pressed-in
     *  slabbed icons in wimps before 3.17
     */

    ER ( swi (OS_ServiceCall, R0, dbox->handle,
                              R1, 0x44ec5,
                              R2, taskhandle, END) );

    ER ( swi (Wimp_DeleteWindow,  R1, dbox,  END) );

    /* free the memory allocated to the window */
    free ((char *) dbox);

    /* and zap the "dbox exists" field */
    gadget->dbox = NULL;

    /* release space occupied by the extension record for unknown gadgets */
    if (gadget->def->type == GADGET_UNKNOWN_TYPE)
    {
        char *bodycopy = (char *) gadget->extension;
        RefDefPtr refs = gadget->def->body.refs;

        /* free space occupied by any strings */
        while (refs->type != REF_END)
        {
            if (refs->type != REF_SPRITE)
                free ( *( (char **) (bodycopy + refs->offset) ) );
            refs++;
        }

        /* and now free the extension record - if any - itself */
        free (bodycopy);
        gadget->extension = NULL;
        gadget->extensionsize = 0;
    }

    /* return input focus to parent editing window */
    return windowedit_focus_claim (gadget->owner);
}


/*
 * Reopen the gadget's properties dbox.
 *
 * Copies coordinates of current position to the prototype;
 *  see gadget_open_dbox(..) above.
 *
 * Called from:
 *   open_window_request(..) in c.main - when an OPEN_REQUEST event is
 *    received from the Wimp (as user moves properties dbox or other windows
 *    over it etc.)
 */

error * gadget_reopen_dbox (WindowPtr win, GadgetPtr gadget)
{
    WindowPtr proto = gadget->def->proto;
    WindowPtr dbox = gadget->dbox;

    dbox->visarea = proto->visarea = win->visarea;
    dbox->scrolloffset = proto->scrolloffset = win->scrolloffset;
    dbox->behind = win->behind;

    return swi (Wimp_OpenWindow, R1, dbox, END);
}


/*
 * Process mouse clicks on the gadget's properties dbox.
 *
 * Called from:
 *   mouse_click(..) in c.main - when the user clicks inside the dbox.
 */

error * gadget_dbox_mouse_click (
    MouseClickPtr mouse,
    unsigned int modifiers,
    GadgetPtr gadget
)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = gadget->dbox;
    GadgetDefPtr def = gadget->def;
    ClickDefPtr click = def->clicks;
    Bool adjustclick = buttons == MB_CLICK(MB_ADJUST);
    Bool menuclick = buttons == MB_CLICK(MB_MENU);
    error *err = NULL;

    /* only interested in clicks */
    if (buttons >= 16)
        return NULL;

    /* deal with any clicks on icons common to all gadget dboxes */
    if (!menuclick)
    {
        if (icon == def->icons.ok)                   /* OK action button */
        {
           err = gadget_apply_dbox (gadget);         /* ... and Cancel   */
           if (err != NULL)
               goto done;
        }
        if (icon == def->icons.ok || icon == def->icons.cancel)
        {
            if (adjustclick)
            {
                err = gadget_init_dbox (gadget);
                goto done;
            }
            else
                return gadget_close_dbox (gadget);
        }

        if (icon == def->icons.hashelp)             /* 'help text' opticon */
        {
            unsigned int flags = dbox_getflags (dbox, def->icons.hashelp);

            GUI_TOGGLE_FADE (dbox, def->icons.help);

            /* if option is now on, place caret in text box */
            if ((flags & IF_SELECTED) != 0)
                return dbox_place_caret (dbox, def->icons.help);
            else
                goto done;
        }

        if (icon == def->icons.helpmaxadjup)        /* helpmax adj up */
        {
            int delta = (modifiers & MODIFIER_SHIFT) ? 10 : 1;
            if (adjustclick)
                delta = -delta;
            err = gui_adjust_len (dbox, def->icons.helpmax,
                                         def->icons.help, delta, 0);
            goto done;
        }

        if (icon == def->icons.helpmaxadjdown)      /* helpmax adj down */
        {
            int delta = (modifiers & MODIFIER_SHIFT) ? 10 : 1;
            if (!adjustclick)
                delta = -delta;
            err = gui_adjust_len (dbox, def->icons.helpmax,
                                         def->icons.help, delta, 0);
            goto done;
        }
    }

    while (click->action != ACT_END)
    {
        if (click->icon == icon)
        {
            if (menuclick)                  /* menu clicks */
                switch (click->action)
                {
                case ACT_FADE:
                case ACT_UNFADE:
                case ACT_TOGGLEFADE:
                case ACT_RADIO:
                case ACT_ADJUST:
                case ACT_ALLOW:
                case ACT_SPECIAL:
                case ACT_SPECIALX:
                    break;

                case ACT_COLOUR:
                    {
                        ColourClickPtr a = &click->params->colour;

                        colours_choose (dbox,
                                        a->displayicon,
                                        a->allowtransparent,
                                        a->menuicon);
                    }
                    break;

                case ACT_BUTTON:
                    {
                        ButtonClickPtr a = &click->params->button;

                        props_button_choose (dbox,
                                             a->displayicon,
                                             a->menuicon,
                                             ICON_BUTTON_MENU);
                    }
                    break;

                default:
                    return error_lookup ("UnkActType", click->action);
                }
            else                            /* adjust or select clicks */
                switch (click->action)
                {
                case ACT_FADE:
                    {
                        FadeClickPtr a = &click->params->fade;

                        dbox_shade (dbox, a->valicon, TRUE);
                    }
                    break;

                case ACT_UNFADE:
                    {
                        UnFadeClickPtr a = &click->params->unfade;

                        dbox_shade (dbox, a->valicon, FALSE);

                        /* place caret in unfaded icon - if it's writable */
                        if (IF_GET_FIELD (TYPE,
                                    dbox->icons[a->valicon].flags) == 15)
                            dbox_place_caret (dbox, a->valicon);
                    }
                    break;

                case ACT_TOGGLEFADE:
                    {
                        ToggleFadeClickPtr a = &click->params->togglefade;

                        GUI_TOGGLE_FADE (dbox, a->valicon);

                        /* place caret in icon - if it's unfaded writable */
                        if ( (dbox_getflags (dbox, a->valicon) &
                          ((IF_TYPE_MASK << IF_TYPE_SHFT) | IF_SHADED)) ==
                                     (15 << IF_TYPE_SHFT) )
                            dbox_place_caret (dbox, a->valicon);
                    }
                    break;

                case ACT_RADIO:
                    /* ensure that ADJ-click on a radio button does not
                        turn it off */
                    if (adjustclick && !dbox_getbutton (dbox, icon))
                        dbox_setbutton (dbox, icon, TRUE);
                    break;

                case ACT_ADJUST:
                    {
                        AdjustClickPtr a = &click->params->adjust;
                        Bool increase = a->increase;
                        int delta = 
                            (modifiers & MODIFIER_SHIFT) ? a->shiftincrement
                                                         : a->increment;

                        if (increase && adjustclick ||
                                 !increase && !adjustclick)
                            delta = -delta;

                        err = gui_adjust_len (dbox, a->lenicon,
                                                    a->valicon, delta, 0);
                    }
                    break;

                case ACT_ALLOW:
                    {
                        AllowClickPtr a = &click->params->allow;
                        Bool on = dbox_getbutton (dbox, icon);
                        Bool shadeval =
                            !on || !dbox_getbutton (dbox, a->otheropticon);

                        dbox_shade (dbox, a->lcicon, !on);
                        dbox_shade (dbox, a->ucicon, !on);
                        dbox_shade (dbox, a->numicon, !on);
                        dbox_shade (dbox, a->otheropticon, !on);
                        dbox_shade (dbox, a->othervalicon, shadeval);

                        /* place caret in writable if unfaded */
                        if (!shadeval)
                            dbox_place_caret (dbox, a->othervalicon);
                    }
                    break;

                case ACT_COLOUR:
                    /* ignore adjust clicks */
                    if (adjustclick)
                        break;

                    {
                        ColourClickPtr a = &click->params->colour;

                        colours_choose (dbox,
                                        a->displayicon,
                                        a->allowtransparent,
                                        a->menuicon);
                    }
                    break;

                case ACT_BUTTON:
                    /* ignore adjust clicks */
                    if (adjustclick)
                        break;

                    {
                        ButtonClickPtr a = &click->params->button;

                        props_button_choose (dbox,
                                             a->displayicon,
                                             a->menuicon,
                                             ICON_BUTTON_MENU);
                    }
                    break;

                case ACT_SPECIAL:
                    {
                        SpecialClickPtr a = &click->params->special;

                        ER ( (a->f) (gadget) );
                    }
                    break;

                case ACT_SPECIALX:
                    {
                        SpecialXClickPtr a = &click->params->specialx;

                        ER ( (a->f) (gadget, icon, mouse) );
                    }
                    break;

                default:
                    return error_lookup ("UnkActType", click->action);
                }
         }

        click++;
    }

  done:
    /* ensure dbox has caret, and that it is not in a faded icon */
    dbox_set_caret_to (dbox, -1);

    return err;
}


/*
 * Process key presses for the gadget's properties dbox.
 *
 * Called from:
 *   key_pressed(..) in c.main - when a key is pressed and the dbox has input
 *    focus.
 */

error * gadget_dbox_key_pressed (
    GadgetPtr gadget,
    KeyPressPtr key,
    Bool *consumed
)
{
    error *err = NULL;

    switch (key->code)
    {
    case 13:                                    /* RETURN */
        interactor_cancel();   /* in case any pop-up is still on display */
        err = gadget_apply_dbox (gadget);
    case 0x1b:                                  /* ESCAPE */
        *consumed = TRUE;
        if (err == NULL)
        {
            if ((wimp_read_modifiers() & MODIFIER_SHIFT) != 0)
                err = gadget_init_dbox (gadget);
            else
                return gadget_close_dbox (gadget);
        }
        break;
    }

    /* ensure dbox has caret, and that it is not in a faded icon */
    dbox_set_caret_to (gadget->dbox, -1);

    return err;
}


/*
 * Update the componentID icon in the gadget's properties dbox.
 *
 * Called from:
 *   disambiguate(..) in c.drag - when a gadget is given a new componentID.
 */

error * gadget_dbox_update_componentid (
    GadgetPtr gadget,
    ComponentID old,
    ComponentID new
)
{
    int icon = gadget->def->icons.id;
    WindowPtr dbox = gadget->dbox;

    /* only update the dbox if it has not already been changed by the user */
    if (icon >= 0 && dbox_getint (dbox, icon) == old)
        dbox_sethex (dbox, icon, new);

    return NULL;
}


/*
 * Update the window_name icon in the gadget's properties dbox.
 *
 * Called from:
 *   windowedit_rename_window(..) - when the gadget's parent window is given
 *    a new name.
 *   finalise_drag(..) in c.drag - when a gadget is moved from one window to
 *    another.
 */

error * gadget_dbox_update_window_name (
    GadgetPtr gadget,
    char *name
)
{
    int icon = gadget->def->icons.window;

    if (icon >= 0)
        dbox_setstring (gadget->dbox, icon, name);

    return NULL;
}


/*
 * Returns TRUE if 'icon' is an acceptable place to drop an object onto in
 *  a properties dialogue box for 'gadget'.
 *
 * Called from protocol_send_resed_object_name_request(..).
 */

Bool gadget_drop_icon (
    GadgetPtr gadget,
    int icon
)
{
    DropDefPtr dropdef = gadget->def->drops;

    /* see if there is an "object dropped" action defined for this icon */
    while (dropdef->icon != -2)
    {
        if (dropdef->icon == icon && dropdef->details->type == OBJECT_DROP)
            return TRUE;

        dropdef++;
    }

    return FALSE;
}


/*
 * Called to process an object of class 'class', name 'name', dropped onto
 *  icon 'icon' in 'gadget's properties dbox.
 *
 * Called from received_resed_object_name(..) in c.protocol.
 */

error * gadget_object_drop (
    GadgetPtr gadget,
    int icon,
    ObjectClass class,
    char *name
)
{
    DropDefPtr dropdef = gadget->def->drops;

    /* find the "object dropped" action defined for this icon */
    while (dropdef->icon != -2)
    {
        if (dropdef->icon == icon && dropdef->details->type == OBJECT_DROP)
        {
            DropDetailsPtr details = dropdef->details;

            /* first check that the class of object dropped is acceptable */
            if (details->class == -1 || details->class == class)
            {
                WindowPtr dbox = gadget->dbox;
                int numextra = 0;

                switch (details->action)
                {
                case DROP_SET:
                    {
                        SetDropPtr d = &details->params->set;

                        dbox_setstring (dbox, d->valicon, name);
                    }
                    break;

                case DROP_SETOPT3:  numextra++;
                case DROP_SETOPT2:  numextra++;
                case DROP_SETOPT:
                    {
                        SetOptDropPtr d = &details->params->setopt;
                        int *extras = (int *)(d + 1);

                        dbox_setstring (dbox, d->valicon, name);
                        dbox_setbutton (dbox, d->opticon, TRUE);

                        /* unshade other fields only if opticon is unfaded */
                        if ((dbox_getflags (dbox, d->opticon)
                                                      & IF_SHADED) == 0)
                        {
                            dbox_shade (dbox, d->valicon, FALSE);
                            while (numextra > 0)
                            {
                                dbox_shade (dbox, *extras, FALSE);
                                extras++;
                                numextra--;
                            }
                        }
                    }
                    break;
                }

                return NULL;
            }
            else
                return error_lookup ("BadDrop");
        }

        dropdef++;
    }

    return NULL;
}


/*
 * Called to process a single gadget dropped onto a gadget properties dbox.
 *
 * 'dest' is the gadget upon whose properties dialogue box 'gadget' has been
 *  dropped; 'icon' identifies the icon onto which the gadget was dropped.
 */

error * gadget_gadget_drop (
    GadgetPtr dest,
    int icon,
    GadgetPtr gadget
)
{
    DropDefPtr dropdef = dest->def->drops;

    /* see if we're interested */
    while (dropdef->icon != -2)
    {
        if (dropdef->icon == icon && dropdef->details->type == GADGET_DROP)
        {
            /* must check that gadget being dropped is "writable" */
            switch (gadget->hdr.type)
            {
            case GADGET_WRITABLE_FIELD:
            case GADGET_NUMBER_RANGE:
            case GADGET_STRING_SET:
                {
                    WindowPtr dbox = dest->dbox;
                    int numextra = 0;

                    /* both gadgets must be in the same window */
                    if (gadget->owner != dest->owner)
                        return error_lookup ("IncompGadDrop");

                    switch (dropdef->details->action)
                    {
                    case DROP_SETOPT3:  numextra++;
                    case DROP_SETOPT2:  numextra++;
                    case DROP_SETOPT:
                        {
                            SetOptDropPtr d =
                                &dropdef->details->params->setopt;
                            int *extras = (int *)(d + 1);

                            dbox_sethex (dbox, d->valicon,
                                               gadget->hdr.componentID);
                            dbox_setbutton (dbox, d->opticon, TRUE);

                            /* unshade other fields if opticon unfaded */
                            if ((dbox_getflags (dbox, d->opticon)
                                                           & IF_SHADED) == 0)
                            {
                                dbox_shade (dbox, d->valicon, FALSE);
                                while (numextra > 0)
                                {
                                    dbox_shade (dbox, *extras, FALSE);
                                    extras++;
                                    numextra--;
                                }
                            }
                        }
                    }
                }
                return NULL;

            default:
                return error_lookup ("BadGadDrop");
            }
        }

        dropdef++;
    }
    
    return NULL;
}


/*
 * Special processing for action buttons - called from gadget_init_dbox(..)
 *  after initialising other icons in the gadget's properties dbox.
 *
 * Fades the "Show as transient" option icon if necessary.
 */

error * gadget_ab_init (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;

    dbox_shade (dbox, I_ACTIONBUTT_SHOW_TRANSIENT,
                !dbox_getbutton (dbox, I_ACTIONBUTT_SHOW));

    return NULL;
}


/*
 * This function is called from gadget_apply_dbox(.) via gadget_ab_apply(.)
 *  before applying the "default button" icon to an action button gadget.
 *
 * There is no need for this function to update the "default" flag in the
 *  gadget since this will be done later; the function can also assume that
 *  that flag still has its original value.
 */

static error * gadget_ab_default (GadgetPtr gadget)
{
    Bool willbedefault = dbox_getbutton (gadget->dbox,
                                         I_ACTIONBUTT_BUTTON_DEFAULT);
    Bool wasdefault = (gadget->hdr.flags & ACTIONBUTTON_ISDEFAULT) != 0;

    if (willbedefault && !wasdefault)
        gadget_note_new_default_action_button (gadget);

    if (wasdefault && !willbedefault)
    {
        /* invalidate before size reduction */
        {
            RectRec bbox = gadget->hdr.bbox;
            if (gadget->selected)
                windowedit_add_ears_to_bbox (&bbox, &bbox);
            wimp_invalidate (gadget->owner->window, &bbox);
        }

        /* reduce size */
        gadget->hdr.bbox.minx += DEFAULT_AB_BORDER_X;
        gadget->hdr.bbox.maxx -= DEFAULT_AB_BORDER_X;
        gadget->hdr.bbox.miny += DEFAULT_AB_BORDER_Y;
        gadget->hdr.bbox.maxy -= DEFAULT_AB_BORDER_Y;
    }
        
    return NULL;
}


/*
 * This function is called whenever a default action button is added to a
 *  a window, or when an existing non-default action button becomes the
 *  default; note that in the latter case the gadget does not yet have
 *  its "isdefault" flag bit set.
 *
 * Called from:
 *   gadget_ab_default(..) - when an action button previously not default
 *    is to become the default.
 *   finalise_drag(..) - when a default action button is copied within its
 *    own window, or when it is moved to another window.
 *
 * Any pre-existing default action button is marked as not default, and any
 *  dbox associated with it reflects this change; the gadget is also
 *  invalidated so that it is redrawn.
 */

error * gadget_note_new_default_action_button (GadgetPtr gadget)
{
    GadgetPtr g;

    {
        g = gadget->owner->gadgets;
        while (g)
        {
            if ( g->hdr.type == GADGET_ACTION_BUTTON &&
                 g != gadget &&
                 (g->hdr.flags & ACTIONBUTTON_ISDEFAULT) )
            {
                /* remove 'default' marker */
                g->hdr.flags &= ~ACTIONBUTTON_ISDEFAULT;

                /* force redraw of gadget */
                {
                    RectRec bbox = g->hdr.bbox;
                    if (g->selected)
                        windowedit_add_ears_to_bbox (&bbox, &bbox);
                    wimp_invalidate (g->owner->window, &bbox);
                }

                /* reduce its size */
                g->hdr.bbox.minx += DEFAULT_AB_BORDER_X;
                g->hdr.bbox.maxx -= DEFAULT_AB_BORDER_X;
                g->hdr.bbox.miny += DEFAULT_AB_BORDER_Y;
                g->hdr.bbox.maxy -= DEFAULT_AB_BORDER_Y;

                /* update the "default" flag in any associated dbox */
                if (g->dbox)
                    dbox_setbutton (g->dbox,
                                    I_ACTIONBUTT_BUTTON_DEFAULT, FALSE);

                break;
            }
            g = g->next;
        }
    }

    /* increase size of new default button */
    if ((gadget->hdr.flags & ACTIONBUTTON_ISDEFAULT) == 0)
    {
        gadget->hdr.bbox.minx -= DEFAULT_AB_BORDER_X;
        gadget->hdr.bbox.maxx += DEFAULT_AB_BORDER_X;
        gadget->hdr.bbox.miny -= DEFAULT_AB_BORDER_Y;
        gadget->hdr.bbox.maxy += DEFAULT_AB_BORDER_Y;
    }

    return NULL;
}


/*
 * This function is called from gadget_apply_dbox(.) via gadget_ab_apply(.)
 *  before applying the "cancel button" icon to an action button gadget.
 *
 * There is no need for this function to update the "cancel" flag in the
 *  gadget since this will be done later; the function can also assume that
 *  that flag still has its original value.
 */

static error * gadget_ab_cancel (GadgetPtr gadget)
{
    Bool willbecancel = dbox_getbutton (gadget->dbox,
                                         I_ACTIONBUTT_BUTTON_CANCEL);
    Bool wascancel = (gadget->hdr.flags & ACTIONBUTTON_ISCANCEL) != 0;

    if (willbecancel && !wascancel)
        gadget_note_new_cancel_action_button (gadget);
        
    return NULL;
}


/*
 * This function is called whenever a cancel action button is added to a
 *  a window, or when an existing non-cancel action button becomes the
 *  cancel button.
 *
 * Called from:
 *   gadget_ab_cancel(..) - when an action button previously not the cancel
 *    button is to become the cancel button.
 *   finalise_drag(..) - when a cancel action button is copied within its
 *    own window, or when it is moved to another window.
 *
 * Any pre-existing cancel action button is marked as not cancel, and any
 *  dbox associated with it reflects this change; note that there is no need
 *  to redraw the gadget because its visual appearance is unaltered.
 */

error * gadget_note_new_cancel_action_button (GadgetPtr gadget)
{
    GadgetPtr g;

    {
        g = gadget->owner->gadgets;
        while (g)
        {
            if ( g->hdr.type == GADGET_ACTION_BUTTON &&
                 g != gadget &&
                 (g->hdr.flags & ACTIONBUTTON_ISCANCEL) )
            {
                /* remove 'cancel' marker */
                g->hdr.flags &= ~ACTIONBUTTON_ISCANCEL;

                /* update the "cancel" flag in any associated dbox */
                if (g->dbox)
                    dbox_setbutton (g->dbox,
                                    I_ACTIONBUTT_BUTTON_CANCEL, FALSE);

                break;
            }
            g = g->next;
        }
    }

    return NULL;
}


/*
 * Special processing for action buttons - called from gadget_apply_dbox(..)
 *  prior to updating any fields of the gadget's body.
 *
 * Calls gadget_ab_default(..) and gadget_ab_cancel(..) to deal with any
 *  changes in the gadget's "default" or "cancel" status.
 */

error * gadget_ab_apply (GadgetPtr gadget)
{
    ER ( gadget_ab_default (gadget) );
    return gadget_ab_cancel (gadget);
}


/*
 * Special processing for labelled boxes - called from gadget_init_dbox(..)
 *  after initialising other icons in the gadget's properties dbox.
 *
 * Deals with the label fields which may be text or sprite.
 */

error * gadget_bx_init (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool istext = (gadget->hdr.flags & LABELLEDBOX_SPRITE) == 0;
    char *val = (char *) gadget->body.labelledbox.label;

    dbox_setstring (dbox,
                    istext ? I_LABELLEDBOX_TEXT : I_LABELLEDBOX_SPRITE,
                    val);
    dbox_setstring (dbox,
                    istext ? I_LABELLEDBOX_SPRITE : I_LABELLEDBOX_TEXT,
                    NULL);
    dbox_setbutton (dbox, I_LABELLEDBOX_ISTEXT, istext);
    dbox_setbutton (dbox, I_LABELLEDBOX_ISSPRITE, !istext);
    dbox_shade (dbox, I_LABELLEDBOX_TEXT, !istext);
    dbox_shade (dbox, I_LABELLEDBOX_SPRITE, istext);
    dbox_shade (dbox, I_LABELLEDBOX_FILLED, istext);

    return NULL;
}


/*
 * Special processing for labelled boxes - called from gadget_apply_dbox(..)
 *  prior to updating any fields of the gadget's body.
 *
 * Deals with the label fields which may be text or sprite.
 */

error *gadget_bx_apply (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    char **field = (char **) &gadget->body.labelledbox.label;
    Bool istext = dbox_getbutton (dbox, I_LABELLEDBOX_ISTEXT);

    if (istext)
        gadget->hdr.flags &= ~LABELLEDBOX_SPRITE;
    else
        gadget->hdr.flags |= LABELLEDBOX_SPRITE;

    gui_get_str (dbox,
                 istext ? I_LABELLEDBOX_TEXT : I_LABELLEDBOX_SPRITE,
                 field);

    return NULL;
}


/*
 * Called from grid_snap (..) to determine the alignment point for a label
 *  gadget.
 */

error * gadget_lb_alignpos (GadgetPtr gadget, int *horiz, int *vert)
{
    /* horizontal alignment is always to the centre of the label */
    *horiz = (int) ALIGNPOS_CENTRE;

    /* vertical alignment depends on the label's justification */
    switch (gadget->hdr.flags & LABEL_JUSTIFICATION)
    {
    default:  /* just in case */
    case LABEL_LEFTJUSTIFY:
        *vert = (int) ALIGNPOS_LEFT;
        break;
    case LABEL_RIGHTJUSTIFY:
        *vert = (int) ALIGNPOS_RIGHT;
        break;
    case LABEL_CENTRED:
        *vert = (int) ALIGNPOS_CENTRE;
        break;
    }
  
    return NULL;
}


/*
 * Called from windowmenu_cb(..) when the user chooses "Make radio group".
 *
 * On entry, the gadgets selected in the window are guaranteed to be one or
 *  more radio buttons.
 */

error * gadget_rb_make_radio_group (WindowObjPtr window)
{
    int groupnum;
    Bool foundoneon = FALSE;
    GadgetPtr gadget = window->gadgets;
    RectRec bbox;

    /* check that they are not already exactly one group */
    while (gadget)
    {
        if (gadget->selected && gadget->hdr.type == GADGET_RADIO_BUTTON)
        {
            groupnum = gadget->body.radiobutton.groupnumber;
            break;
        }
        gadget = gadget->next;
    }
    gadget = window->gadgets;
    while (gadget)
    {
        if (gadget->hdr.type == GADGET_RADIO_BUTTON)
        {
            if (gadget->selected)
            {
               if (groupnum != gadget->body.radiobutton.groupnumber)
                   break;
            }
            else
            {
               if (groupnum == gadget->body.radiobutton.groupnumber)
                   break;
            }
        }
        gadget = gadget->next;
    }
    if (gadget == NULL)
        return NULL;

    /* find an unused group number (one greater than the maximum in use) */
    groupnum = 0;
    gadget = window->gadgets;
    while (gadget)
    {
        if (!gadget->selected &&
                gadget->hdr.type == GADGET_RADIO_BUTTON &&
                groupnum <= gadget->body.radiobutton.groupnumber)
            groupnum = gadget->body.radiobutton.groupnumber + 1;

        gadget = gadget->next;
    }

    bbox.minx = bbox.miny = BIG;
    bbox.maxx = bbox.maxy = -BIG;

    /* make the new radio group - allowing at most one button "on" */
    gadget = window->gadgets;
    while (gadget)
    {
        if (gadget->selected)
        {
            gadget_rb_update_group (gadget, groupnum);          
            if (gadget->hdr.flags & RADIOBUTTON_ON)
            {
                if (foundoneon)
                {
                    gadget->hdr.flags &= ~RADIOBUTTON_ON;
                    wimp_merge_bboxes (&bbox, &bbox, &gadget->hdr.bbox);

                    /* update any associated dialogue on display */
                    if (gadget->dbox)
                        dbox_setbutton (gadget->dbox,
                                            I_RADIOBUTT_SELECTED, FALSE);
                }
                else
                    foundoneon = TRUE;
            }
        }

        gadget = gadget->next;
    }

    if (bbox.minx != BIG)
        wimp_invalidate (window->window, &bbox);

    /* inform shell that window object may have been modified */
    protocol_send_resed_object_modified (window);

    return NULL;
}


/*
 * Called from windowmenu_cb(..) when the user chooses "Select radio group".
 *
 * On entry, the gadgets selected in the window are guaranteed to be one or
 *  more radio buttons that all belong to the same group.
 */

error * gadget_rb_select_radio_group (WindowObjPtr window)
{
    int groupnum;
    GadgetPtr gadget = window->gadgets;
    RectRec bbox;

    bbox.minx = bbox.miny = BIG;
    bbox.maxx = bbox.maxy = -BIG;

    /* find group number of radio group to select */
    while (!gadget->selected)
        gadget = gadget->next;
    groupnum = gadget->body.radiobutton.groupnumber;

    /* make sure all members of the group are selected */
    gadget = window->gadgets;
    while (gadget)
    {
        if (!gadget->selected &&
            gadget->hdr.type == GADGET_RADIO_BUTTON &&
            gadget->body.radiobutton.groupnumber == groupnum)
        {
            gadget->selected = TRUE;
            wimp_merge_bboxes (&bbox, &bbox, &gadget->hdr.bbox);
        }

        gadget = gadget->next;
    }

    /* and force redraw of affected area */
    if (bbox.minx != BIG)
    {
        windowedit_add_ears_to_bbox (&bbox, &bbox);
        wimp_invalidate (window->window, &bbox);
    }
    
    return NULL;
}


/*
 * Special processing for radio buttons - called from gadget_apply_dbox(..)
 *  prior to updating fields of the gadget's body.
 *
 * Makes sure that at most one radio button in the group is switched on.
 */

error *gadget_rb_apply (GadgetPtr gadget)
{
    WindowObjPtr window = gadget->owner;

    /* only need to check if this gadget is being switched on */
    if ((gadget->hdr.flags & RADIOBUTTON_ON) == 0 &&
        dbox_getbutton (gadget->dbox, I_RADIOBUTT_SELECTED))
    {
        int groupnum = gadget->body.radiobutton.groupnumber;
        GadgetPtr gadget = window->gadgets;
        RectRec bbox;

        bbox.minx = bbox.miny = BIG;
        bbox.maxx = bbox.maxy = -BIG;

        while (gadget)
        {
            if (gadget->hdr.type == GADGET_RADIO_BUTTON &&
                (gadget->hdr.flags & RADIOBUTTON_ON) &&
                gadget->body.radiobutton.groupnumber == groupnum)
            {
                gadget->hdr.flags &= ~RADIOBUTTON_ON;
                wimp_merge_bboxes (&bbox, &bbox, &gadget->hdr.bbox);

                /* update any associated dialogue on display */
                if (gadget->dbox)
                    dbox_setbutton (gadget->dbox,
                                        I_RADIOBUTT_SELECTED, FALSE);
            }

            gadget = gadget->next;
        }

        if (bbox.minx != BIG)
            wimp_invalidate (window->window, &bbox);
    }

    return NULL;
}


/*
 * Update "group number" display field inside radio button properties dbox.
 *
 * Called from:
 *    gadget_rb_make_radio_group(..)
 *    finalise_drag(..)
 */

error * gadget_rb_update_group (GadgetPtr gadget, int group)
{
    WindowPtr dbox = gadget->dbox;

    gadget->body.radiobutton.groupnumber = group;

    if (dbox != NULL)
        dbox_setint (dbox, I_RADIOBUTT_GROUP, group);

    return NULL;
}


/*
 * Special processing for sliders - called from gadget_apply_dbox(..) prior
 *  to updating fields of the gadget's body.
 *
 * Checks the limit values set in the dialogue box for sense; any discrepancy
 *  is returned as an error - which will leave the dbox open for the user to
 *  correct.
 */

error *gadget_sl_apply (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    int min = dbox_getint (dbox, I_SLIDER_MINIMUM);
    int max = dbox_getint (dbox, I_SLIDER_MAXIMUM);
    int initial = dbox_getint (dbox, I_SLIDER_INITIAL);
    int stepsize = dbox_getint (dbox, I_SLIDER_STEPSIZE);

    if (max <= min)
        return error_lookup ("Maxbelowmin");

    if (initial < min || initial > max)
        return error_lookup ("InitRange");

    if (stepsize <= 0 || stepsize > (max - min))
        return error_lookup ("StepRange");

    /* if orientation is to change, must alter bounding box */
    {
        Bool ishorizontal = dbox_getbutton (dbox, I_SLIDER_HORIZONTAL);
        Bool washorizontal = (gadget->hdr.flags & SLIDER_VERTICAL) == 0;

        if (ishorizontal && !washorizontal ||
            !ishorizontal && washorizontal)
        {
            RectRec bbox = gadget->hdr.bbox;
            int height = bbox.maxx - bbox.minx;
            int width = bbox.maxy - bbox.miny;
            int centrex = (bbox.minx + bbox.maxx)/2;
            int centrey = (bbox.miny + bbox.maxy)/2;

            /* invalidate area currently occupied by slider */
            if (gadget->selected)
                windowedit_add_ears_to_bbox (&bbox, &bbox);
            wimp_invalidate (gadget->owner->window, &bbox);

            /* rotate through 90 degrees about its centre */
            gadget->hdr.bbox.minx = WIMP_ALIGN_COORD (centrex - width/2);
            gadget->hdr.bbox.maxx = gadget->hdr.bbox.minx + width;
            gadget->hdr.bbox.maxy = WIMP_ALIGN_COORD (centrey + height/2);
            gadget->hdr.bbox.miny = gadget->hdr.bbox.maxy - height;

            /* and this area will be redrawn by gadget_apply_dbox(..) */
        }
    }

    return NULL;
}


/*
 * Special processing for number ranges - called from gadget_init_dbox(..)
 *  after initialising icons in the gadget's properties dbox.
 *
 * Sorts out which icons are shaded and which are not - and also fills in
 *  the icons describing the slider type.
 */

error * gadget_nr_init (GadgetPtr gadget)
{
    unsigned flags = gadget->hdr.flags;
    WindowPtr dbox = gadget->dbox;

    /* fade/unfade icons associated with the display as appropriate */
    {
        Bool hasnumerical = (flags & NUMBERRANGE_NONUMERICALDISPLAY) == 0;
        Bool writable = (flags & NUMBERRANGE_WRITABLE) != 0;
        Bool hasslider =
            (flags & NUMBERRANGE_SLIDERTYPE) != NUMBERRANGE_NOSLIDER;

        dbox_shade (dbox, I_NUMBERRANGE_DISPLAY,
                                        !hasnumerical && !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_WRITABLE,
                                        !hasnumerical && !hasslider);

        dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_LEFT, !hasnumerical);
        dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_CENTRE, !hasnumerical);
        dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_RIGHT, !hasnumerical);

        if (!hasnumerical || !writable)
        {
            dbox_shade (dbox, I_NUMBERRANGE_BEFORE, TRUE);
            dbox_shade (dbox, I_NUMBERRANGE_BEFORE_OBJECT, TRUE);
            dbox_shade (dbox, I_NUMBERRANGE_AFTER, TRUE);
            dbox_shade (dbox, I_NUMBERRANGE_AFTER_OBJECT, TRUE);
        }

        dbox_shade (dbox, I_NUMBERRANGE_DISPLAY_WIDTH,
                              !(hasnumerical && hasslider));
    }

    /* set icons describing slider type, and fade/unfade associated icons */
    {
        int type = flags & NUMBERRANGE_SLIDERTYPE;
        Bool hasslider = type != NUMBERRANGE_NOSLIDER;

        dbox_setbutton (dbox, I_NUMBERRANGE_HASSLIDER, hasslider);
        dbox_setbutton (dbox, I_NUMBERRANGE_RIGHT,
                              type == NUMBERRANGE_SLIDERRIGHT);
        /* if radio buttons are greyed-out, set left by default */
        dbox_setbutton (dbox, I_NUMBERRANGE_LEFT,
                              !hasslider || type == NUMBERRANGE_SLIDERLEFT);

        dbox_shade (dbox, I_NUMBERRANGE_LEFT, !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_RIGHT, !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_BARCOLOUR, !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_BARCOLOUR_POPUP, !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_BACKCOLOUR, !hasslider);
        dbox_shade (dbox, I_NUMBERRANGE_BACKCOLOUR_POPUP, !hasslider);
    }

    return NULL;
}


/*
 * Special processing for number ranges - called from gadget_apply_dbox(..)
 *  prior to updating fields of the gadget's body.
 *
 * Retrieves the value for the slider type.
 * Checks limit values.
 */

error * gadget_nr_apply (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;

    /* must have at least one of slider, display, or adjuster arrows */
    if (!dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY) &&
        !dbox_getbutton (dbox, I_NUMBERRANGE_HASSLIDER)  &&
        !dbox_getbutton (dbox, I_NUMBERRANGE_HASADJUSTERS))
        return error_lookup ("EmptyNR");

    /* check limit values */
    {
        int min = dbox_getint (dbox, I_NUMBERRANGE_MINIMUM);
        int max = dbox_getint (dbox, I_NUMBERRANGE_MAXIMUM);
        int initial = dbox_getint (dbox, I_NUMBERRANGE_INITIAL);
        int stepsize = dbox_getint (dbox, I_NUMBERRANGE_STEPSIZE);
        int precision = dbox_getint (dbox, I_NUMBERRANGE_PRECISION);

        if (max <= min)
            return error_lookup ("Maxbelowmin");

        if (initial < min || initial > max)
            return error_lookup ("InitRange");

        if (stepsize <= 0 || stepsize > (max - min))
            return error_lookup ("StepRange");

        if (precision < 0 || precision > 10)
            return error_lookup ("PrecisionRange");
    }

    /* check display field length if appropriate */
    if ((dbox_getflags (dbox, I_NUMBERRANGE_DISPLAY_WIDTH) & IF_SHADED) == 0)
    {
        int len = WIMP_ALIGN_COORD (
                      dbox_getint (dbox, I_NUMBERRANGE_DISPLAY_WIDTH));

        if (len < 16 ||
            len > (gadget->hdr.bbox.maxx - gadget->hdr.bbox.minx) - 16)
            return error_lookup ("NrDispWidth");

        /* reinstate aligned value */
        dbox_setint (dbox, I_NUMBERRANGE_DISPLAY_WIDTH, len);
    }

    /* retrieve slider type */
    {
        unsigned type;

        if (dbox_getbutton (dbox, I_NUMBERRANGE_HASSLIDER))
            type = dbox_getbutton (dbox, I_NUMBERRANGE_LEFT) ?
                     NUMBERRANGE_SLIDERLEFT : NUMBERRANGE_SLIDERRIGHT;
        else
            type = NUMBERRANGE_NOSLIDER;

        gadget->hdr.flags = type | 
                            gadget->hdr.flags & ~NUMBERRANGE_SLIDERTYPE;
    }

    return NULL;
}


/*
 * Special processing for number ranges - called when the user clicks adjust
 *  or select on the "has slider" icon.
 *
 * Fades or unfades associated icons as necessary.
 */

error * gadget_nr_slider (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool hasslider = dbox_getbutton (dbox, I_NUMBERRANGE_HASSLIDER);
    Bool hasnumerical = dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY);
    Bool shadewidth = !(hasslider && hasnumerical);

    /* (un)fade associated icons */
    dbox_shade (dbox, I_NUMBERRANGE_LEFT, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_RIGHT, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_BARCOLOUR, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_BARCOLOUR_POPUP, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_BACKCOLOUR, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_BACKCOLOUR_POPUP, !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_DISPLAY_WIDTH, shadewidth);
    dbox_shade (dbox, I_NUMBERRANGE_DISPLAY, !hasnumerical && !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_WRITABLE, !hasnumerical && !hasslider);


    if (!shadewidth)
        dbox_place_caret (dbox, I_NUMBERRANGE_DISPLAY_WIDTH);

    return NULL;
}


/*
 * Special processing for number ranges - called when the user clicks adjust
 *  or select on the "has numerical display" icon.
 *
 * Fades or unfades associated fields as necessary.
 */

error * gadget_nr_numerical (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool hasnumerical = dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY);
    Bool hasslider = dbox_getbutton (dbox, I_NUMBERRANGE_HASSLIDER);
    Bool shadewidth = !(hasnumerical && hasslider);

    /* (un)fade immediately associated icons */
    dbox_shade (dbox, I_NUMBERRANGE_DISPLAY, !hasnumerical && !hasslider);
    dbox_shade (dbox, I_NUMBERRANGE_WRITABLE, !hasnumerical && !hasslider);

    dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_LEFT, !hasnumerical);
    dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_CENTRE, !hasnumerical);
    dbox_shade (dbox, I_NUMBERRANGE_JUSTIFY_RIGHT, !hasnumerical);
    dbox_shade (dbox, I_NUMBERRANGE_DISPLAY_WIDTH, shadewidth);

    /* deal with before/after icons */
    {
        Bool haswritable = 
            hasnumerical && dbox_getbutton (dbox, I_NUMBERRANGE_WRITABLE);

        dbox_shade (dbox, I_NUMBERRANGE_BEFORE, !haswritable);
        dbox_shade (dbox, I_NUMBERRANGE_AFTER, !haswritable);
 
        dbox_shade (dbox, I_NUMBERRANGE_BEFORE_OBJECT,
            !haswritable || !dbox_getbutton (dbox, I_NUMBERRANGE_BEFORE));
        dbox_shade (dbox, I_NUMBERRANGE_AFTER_OBJECT,
            !haswritable || !dbox_getbutton (dbox, I_NUMBERRANGE_AFTER));
    }

    if (!shadewidth)
        dbox_place_caret (dbox, I_NUMBERRANGE_DISPLAY_WIDTH);

    return NULL;
}


/*
 * Special processing for number ranges - called when the user clicks adjust
 *  or select on the "display" radio button.
 *
 * Fades associated fields as necessary.
 */

error * gadget_nr_display (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool hasnumerical = dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY);

    if (hasnumerical)
    {
        dbox_shade (dbox, I_NUMBERRANGE_BEFORE, TRUE);
        dbox_shade (dbox, I_NUMBERRANGE_BEFORE_OBJECT, TRUE);
        dbox_shade (dbox, I_NUMBERRANGE_AFTER, TRUE);
        dbox_shade (dbox, I_NUMBERRANGE_AFTER_OBJECT, TRUE);
    }

    return NULL;
}


/*
 * Special processing for number ranges - called when the user clicks adjust
 *  or select on the "writable" radio button.
 *
 * Unfades associated fields as necessary.
 */

error * gadget_nr_writable (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool caretplaced = FALSE;
    Bool hasnumerical = dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY);

    if (hasnumerical)
    {
        dbox_shade (dbox, I_NUMBERRANGE_BEFORE, FALSE);
        if (dbox_getbutton (dbox, I_NUMBERRANGE_BEFORE))
        {
            dbox_shade (dbox, I_NUMBERRANGE_BEFORE_OBJECT, FALSE);
            dbox_place_caret (dbox, I_NUMBERRANGE_BEFORE_OBJECT);
            caretplaced = TRUE;
        }
        dbox_shade (dbox, I_NUMBERRANGE_AFTER, FALSE);
        if (dbox_getbutton (dbox, I_NUMBERRANGE_AFTER))
        {
            dbox_shade (dbox, I_NUMBERRANGE_AFTER_OBJECT, FALSE);
            if (!caretplaced)
                dbox_place_caret (dbox, I_NUMBERRANGE_AFTER_OBJECT);
        }
    }

    return NULL;
}


/*
 * Updates display width - called from resize_selected_gadgets(..) at the end
 *  of a drag on a numberrange's internal resize ear.
 */

error * gadget_nr_update_display_width (GadgetPtr gadget, int width)
{
    WindowPtr dbox = gadget->dbox;

    if (dbox != NULL)
        dbox_setint (dbox, I_NUMBERRANGE_DISPLAY_WIDTH, width);

    return NULL;
}


/*
 * Special processing for string sets - called from gadget_init_dbox(..)
 *  after initialising icons in the gadget's properties dbox.
 *
 * Sorts out which icons are shaded and which are not.
 */

error * gadget_ss_init (GadgetPtr gadget)
{
    unsigned flags = gadget->hdr.flags;
    WindowPtr dbox = gadget->dbox;
    Bool hasnumerical = (flags & STRINGSET_NODISPLAY) == 0;
    Bool writable = (flags & STRINGSET_WRITABLE) != 0;

    /* fade/unfade icons associated with the display as appropriate */
    dbox_shade (dbox, I_STRINGSET_DISPLAY, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_WRITABLE, !hasnumerical);

    dbox_shade (dbox, I_STRINGSET_JUSTIFY_LEFT, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_JUSTIFY_CENTRE, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_JUSTIFY_RIGHT, !hasnumerical);

    if (!hasnumerical || !writable)
    {
        dbox_shade (dbox, I_STRINGSET_BEFORE, TRUE);
        dbox_shade (dbox, I_STRINGSET_BEFORE_OBJECT, TRUE);
        dbox_shade (dbox, I_STRINGSET_AFTER, TRUE);
        dbox_shade (dbox, I_STRINGSET_AFTER_OBJECT, TRUE);

        dbox_shade (dbox, I_STRINGSET_HASALLOWED, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_UP, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_DOWN, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_LC, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_UC, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_NUMERIC, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_OTHER, TRUE);
        dbox_shade (dbox, I_STRINGSET_ALLOWED, TRUE);
    }

    return NULL;
}


/*
 * Special processing for string sets - called when the user clicks adjust
 *  or select on the "has display field" icon.
 *
 * Fades or unfades associated fields as necessary.
 */

error * gadget_ss_numerical (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool hasnumerical = dbox_getbutton (dbox, I_STRINGSET_HASDISPLAY);

    /* (un)fade immediately associated icons */
    dbox_shade (dbox, I_STRINGSET_DISPLAY, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_WRITABLE, !hasnumerical);

    dbox_shade (dbox, I_STRINGSET_JUSTIFY_LEFT, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_JUSTIFY_CENTRE, !hasnumerical);
    dbox_shade (dbox, I_STRINGSET_JUSTIFY_RIGHT, !hasnumerical);

    /* deal with 'before/after' icons and 'allowable' icons */
    {
        Bool haswritable = 
            hasnumerical && dbox_getbutton (dbox, I_STRINGSET_WRITABLE);
        Bool hasallowable =
            haswritable && dbox_getbutton (dbox, I_STRINGSET_HASALLOWED);
        Bool shadebefore =
            !haswritable || !dbox_getbutton (dbox, I_STRINGSET_BEFORE);
        Bool shadeafter =
            !haswritable || !dbox_getbutton (dbox, I_STRINGSET_AFTER);
        Bool shadeallowed =
          !hasallowable || !dbox_getbutton (dbox, I_STRINGSET_ALLOWED_OTHER);

        dbox_shade (dbox, I_STRINGSET_BEFORE, !haswritable);
        dbox_shade (dbox, I_STRINGSET_AFTER, !haswritable);
 
        dbox_shade (dbox, I_STRINGSET_BEFORE_OBJECT, shadebefore);
        dbox_shade (dbox, I_STRINGSET_AFTER_OBJECT, shadeafter);

        dbox_shade (dbox, I_STRINGSET_HASALLOWED, !haswritable);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX, !haswritable);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_UP, !haswritable);
        dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_DOWN, !haswritable);

        dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_LC, !hasallowable);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_UC, !hasallowable);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_NUMERIC, !hasallowable);
        dbox_shade (dbox, I_STRINGSET_ALLOWED_OTHER, !hasallowable);

        dbox_shade (dbox, I_STRINGSET_ALLOWED, shadeallowed);

        if (!shadeallowed)
            dbox_place_caret (dbox, I_STRINGSET_ALLOWED);
        else
        if (!shadebefore)
            dbox_place_caret (dbox, I_STRINGSET_BEFORE_OBJECT);
        else
        if (!shadeafter)
            dbox_place_caret (dbox, I_STRINGSET_AFTER_OBJECT);
    }

    return NULL;
}


/*
 * Special processing for string sets - called when the user clicks adjust
 *  or select on the "display" radio button.
 *
 * Fades associated fields as necessary.
 */

error * gadget_ss_display (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;

    dbox_shade (dbox, I_STRINGSET_BEFORE, TRUE);
    dbox_shade (dbox, I_STRINGSET_BEFORE_OBJECT, TRUE);
    dbox_shade (dbox, I_STRINGSET_AFTER, TRUE);
    dbox_shade (dbox, I_STRINGSET_AFTER_OBJECT, TRUE);

    dbox_shade (dbox, I_STRINGSET_HASALLOWED, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_UP, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_DOWN, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_LC, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_UC, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_NUMERIC, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_OTHER, TRUE);
    dbox_shade (dbox, I_STRINGSET_ALLOWED, TRUE);

    return NULL;
}


/*
 * Special processing for string sets - called when the user clicks adjust
 *  or select on the "writable" radio button.
 *
 * Unfades associated fields as necessary.
 */

error * gadget_ss_writable (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    Bool hasallowed = dbox_getbutton (dbox, I_STRINGSET_HASALLOWED);
    Bool hasbefore = dbox_getbutton (dbox, I_STRINGSET_BEFORE);
    Bool hasafter = dbox_getbutton (dbox, I_STRINGSET_AFTER);
    Bool hasother =
        hasallowed && dbox_getbutton (dbox, I_STRINGSET_ALLOWED_OTHER);

    dbox_shade (dbox, I_STRINGSET_BEFORE, FALSE);
    if (hasbefore)
        dbox_shade (dbox, I_STRINGSET_BEFORE_OBJECT, FALSE);
    dbox_shade (dbox, I_STRINGSET_AFTER, FALSE);
    if (hasafter)
        dbox_shade (dbox, I_STRINGSET_AFTER_OBJECT, FALSE);

    dbox_shade (dbox, I_STRINGSET_HASALLOWED, FALSE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX, FALSE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_UP, FALSE);
    dbox_shade (dbox, I_STRINGSET_ALLOWEDMAX_ADJ_DOWN, FALSE);

    dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_LC, !hasallowed);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_ALPHA_UC, !hasallowed);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_NUMERIC, !hasallowed);
    dbox_shade (dbox, I_STRINGSET_ALLOWED_OTHER, !hasallowed);

    dbox_shade (dbox, I_STRINGSET_ALLOWED, !hasother);

    if (hasother)
        dbox_place_caret (dbox, I_STRINGSET_ALLOWED);
    else
    if (hasbefore)
        dbox_place_caret (dbox, I_STRINGSET_BEFORE_OBJECT);
    else
    if (hasafter)
        dbox_place_caret (dbox, I_STRINGSET_AFTER_OBJECT);

    return NULL;
}


/*
 * This function returns the minimum value for the maxselectedstringlen field
 *  in the given gadget.
 *
 * It is called as part of FLD_LENGTH processing - from gadget_load(..) and
 *  gadget_apply_dbox(..) - when determining a suitable length value.
 */

int gadget_ss_min_maxselectedstringlen (GadgetPtr gadget)
{
    int min = 0;
    char *init = (char *) gadget->body.stringset.initialselectedstring;
    char *s = (char *) gadget->body.stringset.stringset;
    char *t = s;
    int len;
    int skipped = 0;

    if (init != NULL)
        min = strlen (init);

    if (s != NULL)
    {
        while (*t != 0)
        {
            switch (*t)
            {
            case '\\':
                if (*(t + 1) != 0)
                {
                    skipped++;
                    t++;
                }
                break;

            case ',':
                *t = 0;
                len = strlen (s) - skipped;
                if (min < len)
                    min = len;
                *t = ',';
                s = t + 1;
                skipped = 0;
                break;
            }
            t++;
        }

        len = strlen (s) - skipped;
        if (min < len)
            min = len;
    }

    return min + 1;
}


/*
 * Special processing for button gadgets - called from gadget_apply_dbox(..)
 *  prior to updating fields of the gadget's body.
 *
 * Checks limit values.
 */

error * gadget_wi_apply (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    int esg = dbox_getint (dbox, I_WIMPICON_ESG);

    if (esg < 0 || esg > 31)
        return error_lookup ("ESGRange");

    return NULL;
}


/*
 * Returns the type of the field at 'offset' inside the unknown gadget whose 
 *  definition is 'def'; possible values are:
 *      REF_STR, REF_MSG, REF_SPRITE, REF_END
 *  where REF_END means that the field is not relocatable (and hence is to
 *  be treated as an integer).
 */

static int uk_field_type (GadgetDefPtr def, int offset)
{
    RefDefPtr refs = def->body.refs;

    while (refs->type != REF_END)
    {
        if (refs->offset == offset)
            return refs->type;

        refs++;
    }

    return REF_END;
}


/*
 * Copies a value from the dialogue box into the field at 'offset' inside an
 *  unknown gadget.
 */

static error * save_uk_field (GadgetPtr gadget, int offset)
{
    char *field = (char *) gadget->extension + offset;
    int type = uk_field_type (gadget->def, offset);
    WindowPtr dbox = gadget->dbox;
    int v;
    char *s = 0;

    switch (type)
    {
        case REF_STR:
            if (dbox_getbutton (dbox, I_UNKNOWN_HASSTRING))
                s = dbox_getstring (dbox, I_UNKNOWN_STRING);
            break;

        case REF_MSG:
            if (dbox_getbutton (dbox, I_UNKNOWN_HASMESSAGE))
                s = dbox_getstring (dbox, I_UNKNOWN_MESSAGE);
            break;

        case REF_SPRITE:
            if (dbox_getbutton (dbox, I_UNKNOWN_WIMP))
                v = WIMP_SPRITEAREA;
            else
                v = CLIENT_SPRITEAREA;
            break;
 
        case REF_END:
            v = dbox_getint (dbox, I_UNKNOWN_INTEGER);
            break;
    }

    switch (type)
    {
        case REF_STR:
        case REF_MSG:
            /* note that each of these operations also works for s = NULL! */
            free (* ((char **) field));
            * ((char **) field) = s;
            ER (clonestring ((char **) field));
            break;

        case REF_SPRITE:
        case REF_END:
            * ((int *) field) = v;
            break; 
    }

    return NULL;
}


/*
 * Loads the value of the unknown gadget's field at 'offset' into its
 *  properties dialogue box.
 */

static error * load_uk_field (GadgetPtr gadget, int offset)
{
    char *field = (char *) gadget->extension + offset;
    int type = uk_field_type (gadget->def, offset);
    WindowPtr dbox = gadget->dbox;

    dbox_shade (dbox, I_UNKNOWN_INTEGER, type != REF_END);
    dbox_shade (dbox, I_UNKNOWN_HASSTRING, type != REF_STR);
    dbox_shade (dbox, I_UNKNOWN_STRING, type != REF_STR);
    dbox_shade (dbox, I_UNKNOWN_HASMESSAGE, type != REF_MSG);
    dbox_shade (dbox, I_UNKNOWN_MESSAGE, type != REF_MSG);
    dbox_shade (dbox, I_UNKNOWN_WIMP, type != REF_SPRITE);
    dbox_shade (dbox, I_UNKNOWN_CLIENT, type != REF_SPRITE);

    /* only shade the labels from 3.50 onwards */
    if (wimpversion >= 350)
    {
        dbox_shade (dbox, I_UNKNOWN_INTEGERLABEL, type != REF_END);
        dbox_shade (dbox, I_UNKNOWN_SPRITELABEL, type != REF_SPRITE);
    }

    if (type != REF_END)
        dbox_setstring (dbox, I_UNKNOWN_INTEGER, "");
    if (type != REF_STR)
    {
        dbox_setstring (dbox, I_UNKNOWN_STRING, "");
        dbox_setbutton (dbox, I_UNKNOWN_HASSTRING, FALSE);
    }
    if (type != REF_MSG)
    {
        dbox_setstring (dbox, I_UNKNOWN_MESSAGE, "");
        dbox_setbutton (dbox, I_UNKNOWN_HASMESSAGE, FALSE);
    }

    switch (type)
    {
        case REF_STR:
            {
                char *s = * ((char **) field);

                dbox_setstring (dbox, I_UNKNOWN_STRING, s);
                dbox_setbutton (dbox, I_UNKNOWN_HASSTRING, s != NULL);
                if (s == NULL)
                    dbox_shade (dbox, I_UNKNOWN_STRING, TRUE);
                else
                    dbox_place_caret (dbox, I_UNKNOWN_STRING);
            }
            break;

        case REF_MSG:
            {
                char *s = * ((char **) field);

                dbox_setstring (dbox, I_UNKNOWN_MESSAGE, s);
                dbox_setbutton (dbox, I_UNKNOWN_HASMESSAGE, s != NULL);
                if (s == NULL)
                    dbox_shade (dbox, I_UNKNOWN_MESSAGE, TRUE);
                else
                    dbox_place_caret (dbox, I_UNKNOWN_MESSAGE);
            }
            break;

        case REF_SPRITE:
            {
                Bool iswimp = (* ((int *) field) == WIMP_SPRITEAREA);

                dbox_setbutton (dbox, I_UNKNOWN_WIMP, iswimp);
                dbox_setbutton (dbox, I_UNKNOWN_CLIENT, !iswimp);
            }
            break;

        case REF_END:        /* ie an integer */
            dbox_setint (dbox, I_UNKNOWN_INTEGER, * ((int *) field));
            dbox_place_caret (dbox, I_UNKNOWN_INTEGER);
            break;
    }

    return NULL;
}


/*
 * Special processing for unknown gadgets - called from gadget_init_dbox(..)
 *  after initialising icons in the gadget's properties dbox.
 *
 * Fills in "Other flags" field, and sorts out the "Other fields" section.
 */

error * gadget_uk_init (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    GadgetDefPtr def = gadget->def;
    char *copy = (char *) gadget->extension;
    char *body = (char *) &gadget->body;

    /* initialise "Other flags" field */
    dbox_sethex (dbox, I_UNKNOWN_FLAGS,
                       gadget->hdr.flags & ~(GADGET_FADED | GADGET_ATBACK));


    /* special case: gadget with no body */
    if (copy == NULL)
    {
        /* grey out all fields other than header ones */
        static int fields[] = {
            I_UNKNOWN_OFFSET,
            I_UNKNOWN_INTEGER,
            I_UNKNOWN_HASSTRING,
            I_UNKNOWN_STRING,
            I_UNKNOWN_HASMESSAGE,
            I_UNKNOWN_MESSAGE,
            I_UNKNOWN_WIMP,
            I_UNKNOWN_CLIENT,
            I_UNKNOWN_OFFSET_ADJ_UP,
            I_UNKNOWN_OFFSET_ADJ_DOWN,
            I_UNKNOWN_OFFSETLABEL,
            I_UNKNOWN_INTEGERLABEL,
            I_UNKNOWN_SPRITELABEL,
            0 };
        int *pf = fields;

        /* don't grey out the labels for older wimps */
        if (wimpversion < 350)
            fields[10] = 0;

        while (*pf != 0)
        {
            dbox_shade (dbox, *pf, TRUE);
            pf++;
        }

        /* blank out the "Offset" value */
        dbox_setstring (dbox, I_UNKNOWN_OFFSET, "");

        return NULL;
    }


    /*
     * (Re)-initialise the extension record:
     *   Free any strings already there.
     *   Copy the gadget's body to it.
     *   Clone any string fields inside it.
     */
    {
        RefDefPtr refs = def->body.refs;

        /* free strings already there */
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    free (* ((char **) (copy + refs->offset)));
                    break;
            }

            refs++;
        }

        /* copy across body */
        memcpy (copy, body, def->body.size);

        /* clone any strings */
        refs = def->body.refs;
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    ER ( clonestring ((char **) (copy + refs->offset)) );
                    break;
            }

            refs++;
        }
    }

    /* reload fields in "Other fields" section of dbox */
    {
        int offset = dbox_getint (dbox, I_UNKNOWN_OFFSET);

        return load_uk_field (gadget, offset);
    }
}


/*
 * User has requested that the "offset" be changed by 'delta':
 *  Check that offset remains in the range 0 <= offset < size.
 *  Save any change to the field at the current offset.
 *  Load the value of the field at the new offset.
 *  Update the "offset" field.
 */

static error * gadget_uk_offset_change (GadgetPtr gadget, int delta)
{
    WindowPtr dbox = gadget->dbox;
    int curr = dbox_getint (dbox, I_UNKNOWN_OFFSET);
    int new = curr + delta;

    /* ensure offset remains in range */
    if (new < 0)
        new = 0;

    if (new >= gadget->def->body.size)
        new = gadget->def->body.size - 4;

    /* return immediately if no change */
    if (curr == new)
        return NULL;

    /* display new offset */
    dbox_setint (dbox, I_UNKNOWN_OFFSET, new);

    /* save any changes to the extension record before ... */
    ER ( save_uk_field (gadget, curr) );

    /* ... updating the dbox with the new field's value */
    ER ( load_uk_field (gadget, new) );

    return NULL;
}


/*
 * Special processing for unknown gadgets - called when the user clicks
 *  adjust or select on the "offset" up- or down-adjuster icon.
 *
 * This function determines what offset adjustment is required, and then
 *  calls gadget_uk_offset_change(..) to update the "Other fields" section.
 */

error * gadget_uk_offset (GadgetPtr gadget, int icon, MouseClickPtr mouse)
{
    Bool up = (icon == I_UNKNOWN_OFFSET_ADJ_UP);
    Bool adjust = (mouse->buttons == MB_CLICK(MB_ADJUST));
    Bool shift = (wimp_read_modifiers () & MODIFIER_SHIFT) != 0;
    int delta = 4;

    if (!up)
        delta = -delta;

    if (adjust)
        delta = -delta;

    if (shift)
        delta *= 10;

    return gadget_uk_offset_change (gadget, delta);
}


/*
 * Special processing for unknown gadgets - called from gadget_apply_dbox(..)
 *  prior to updating fields of the gadget's body.
 *
 * Processes "Other flags" and "Other fields" sections.
 */

error * gadget_uk_apply (GadgetPtr gadget)
{
    WindowPtr dbox = gadget->dbox;
    GadgetDefPtr def = gadget->def;
    char *copy = (char *) gadget->extension;
    char *body = (char *) &gadget->body;
    unsigned int otherflags = dbox_getint (dbox, I_UNKNOWN_FLAGS);
    unsigned int mask = (GADGET_FADED | GADGET_ATBACK);

    /* update the flags field */
    gadget->hdr.flags = (gadget->hdr.flags & mask) | (otherflags & ~mask);


    /* special case - gadget has zero body size */
    if (copy == NULL)
        return NULL;


    /* update the extension record from the dbox */
    {
        int offset = dbox_getint (dbox, I_UNKNOWN_OFFSET);

        ER ( save_uk_field (gadget, offset) );
    }

    /*
     * Update the gadget's body from the extension record:
     *   Free any strings already there.
     *   Copy the extension record to it.
     *   Clone any string fields inside it.
     */
    {
        RefDefPtr refs = def->body.refs;

        /* free strings already there */
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    free (* ((char **) (body + refs->offset)));
                    break;
            }

            refs++;
        }

        /* copy across extension record */
        memcpy (body, copy, def->body.size);

        /* clone any strings */
        refs = def->body.refs;
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    ER ( clonestring ((char **) (body + refs->offset)) );
                    break;
            }

            refs++;
        }
    }
    
    return NULL;
}


/*
 * Called by qsort to compare two writable icons.
 * p and q are pointers to GadgetPtr's, and p is greater than q if its gadget
 *   - is completely below q's gadget
 *   - overlaps q's gadget vertically, but is completely to its right
 * Otherwise the order of the two gadgets is not defined (they are "equal").
 */

static int compare_writables (const void *p, const void *q)
{
    GadgetPtr g = * (GadgetPtr *)p;
    GadgetPtr h = * (GadgetPtr *)q;

    if (g->hdr.bbox.miny > h->hdr.bbox.maxy)
        return -1;
    if (g->hdr.bbox.maxy < h->hdr.bbox.miny)
        return 1;
    if (g->hdr.bbox.maxx < h->hdr.bbox.minx)
        return -1;
    if (g->hdr.bbox.minx > h->hdr.bbox.maxx)
        return 1;

    return 0;
}


/*
 * Called from windowmenu_cb(..) when user chooses "Link writables".
 * On entry, the selection includes at least one writable, stringset or
 *  numberrange gadget - unless it was called as a result of ^L in which
 *  case there may not even be a current selection.
 */

error * gadget_link_writables (WindowObjPtr window)
{
    int n = 0;                /* number of writables in the selection */
    GadgetPtr *writables;     /* will be the array to sort */
    GadgetPtr gadget = window->gadgets;
    int i;
    Bool modified = FALSE;

    /* count the "writables" in the selection */
    while (gadget != NULL)
    {
        if ( gadget->selected &&
               (gadget->hdr.type == GADGET_WRITABLE_FIELD ||
                gadget->hdr.type == GADGET_STRING_SET ||
                gadget->hdr.type == GADGET_NUMBER_RANGE) )
            n++;

        gadget = gadget->next;
    }

    /* might be zero - if called by keyboard shortcut */
    if (n == 0)
        return error_lookup ("Needwritable");

    /* allocate and initialise qsort array */
    writables = (GadgetPtr *) malloc (n * sizeof (GadgetPtr));
    if (writables == NULL)
        return (error_lookup ("NoMem") );

    n = 0;
    gadget = window->gadgets;
    while (gadget != NULL)
    {
        if ( gadget->selected &&
               (gadget->hdr.type == GADGET_WRITABLE_FIELD ||
                gadget->hdr.type == GADGET_STRING_SET ||
                gadget->hdr.type == GADGET_NUMBER_RANGE) )
            writables[n++] = gadget;

        gadget = gadget->next;
    }

    /* sort writables according to co-ordinates of bottom lh corner */
    qsort ((void *)writables, n, sizeof (GadgetPtr), compare_writables);

    /* set "before" and "after" fields accordingly */
    for (i = 0; i < n; i++)
    {
        GadgetPtr prevgadget = writables [ ((i == 0) ? n : i) - 1];
        GadgetPtr nextgadget = writables [ (i == (n - 1)) ? 0 : (i + 1)];
        int previd = prevgadget->hdr.componentID;
        int nextid = nextgadget->hdr.componentID;
        GadgetPtr gadget = writables [i];
        WindowPtr dbox = gadget->dbox;

        switch (gadget->hdr.type)
        {
        case GADGET_WRITABLE_FIELD:
            if (gadget->body.writablefield.before != previd ||
                gadget->body.writablefield.after != nextid)
            {
                modified = TRUE;
                gadget->body.writablefield.before = previd;
                gadget->body.writablefield.after = nextid;
                if (dbox)
                {
                    dbox_setbutton (dbox, I_WRITABLE_BEFORE, TRUE);
                    dbox_sethex (dbox, I_WRITABLE_BEFORE_OBJECT, previd);
                    dbox_shade (dbox, I_WRITABLE_BEFORE_OBJECT, FALSE);
                    dbox_setbutton (dbox, I_WRITABLE_AFTER, TRUE);
                    dbox_sethex (dbox, I_WRITABLE_AFTER_OBJECT, nextid);
                    dbox_shade (dbox, I_WRITABLE_AFTER_OBJECT, FALSE);
                }
            }
            break;

        case GADGET_STRING_SET:
            if (gadget->body.stringset.before != previd ||
                gadget->body.stringset.after != nextid)
            {
                modified = TRUE;
                gadget->body.stringset.before = previd;
                gadget->body.stringset.after = nextid;
                if (dbox)
                {
                    dbox_setbutton (dbox, I_STRINGSET_BEFORE, TRUE);
                    dbox_sethex (dbox, I_STRINGSET_BEFORE_OBJECT, previd);
                    dbox_setbutton (dbox, I_STRINGSET_AFTER, TRUE);
                    dbox_sethex (dbox, I_STRINGSET_AFTER_OBJECT, nextid);
                    if ( dbox_getbutton (dbox, I_STRINGSET_HASDISPLAY) &&
                         dbox_getbutton (dbox, I_STRINGSET_WRITABLE) )
                    {
                        dbox_shade (dbox, I_STRINGSET_BEFORE_OBJECT, FALSE);
                        dbox_shade (dbox, I_STRINGSET_AFTER_OBJECT, FALSE);
                    }
                }
            }
            break;

        case GADGET_NUMBER_RANGE:
            if (gadget->body.numberrange.before != previd ||
                gadget->body.numberrange.after != nextid)
            {
                modified = TRUE;
                gadget->body.numberrange.before = previd;
                gadget->body.numberrange.after = nextid;
                if (dbox)
                {
                    dbox_setbutton (dbox, I_NUMBERRANGE_BEFORE, TRUE);
                    dbox_sethex (dbox, I_NUMBERRANGE_BEFORE_OBJECT, previd);
                    dbox_setbutton (dbox, I_NUMBERRANGE_AFTER, TRUE);
                    dbox_sethex (dbox, I_NUMBERRANGE_AFTER_OBJECT, nextid);
                    if ( dbox_getbutton (dbox, I_NUMBERRANGE_HASDISPLAY) &&
                         dbox_getbutton (dbox, I_NUMBERRANGE_WRITABLE) )
                    {
                        dbox_shade (dbox, I_NUMBERRANGE_BEFORE_OBJECT, FALSE);
                        dbox_shade (dbox, I_NUMBERRANGE_AFTER_OBJECT, FALSE);
                    }
                }
            }
            break;
        }
    }

    /* free the space used to sort in */
    free (writables);

    /* inform shell if window object has been modified */
    if (modified)
        protocol_send_resed_object_modified (window);

    return NULL;
}
